mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 00:29:19 +02:00
1372 lines
48 KiB
C++
1372 lines
48 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "world/Sweep.h"
|
|
#include "editor/editorGauge.hpp"
|
|
|
|
#include "model/MdlMngr.h"
|
|
#include "model/Model3d.h"
|
|
#include "rendering/renderer.h"
|
|
#include "scene/scene.h"
|
|
#include "simulation/simulation.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "utilities/parser.h"
|
|
#include "vehicle/DynObj.h"
|
|
#include "world/Track.h"
|
|
|
|
#include <glm/gtx/rotate_vector.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <functional>
|
|
#include <optional>
|
|
|
|
namespace
|
|
{
|
|
|
|
double const sample_spacing{0.5}; // m along the curve between the samples
|
|
std::size_t const vertex_limit{4000000}; // copies beyond this many vertices are left out
|
|
double const slice_width{1.0}; // m of the model, the faces of a bent copy are cut into
|
|
double const edge_band{0.3}; // m from the side facing the curve, where the top of the edge is looked for
|
|
double const outer_reach{30.0};
|
|
double const overhang_band{0.15};
|
|
double const overhang_depth{0.05};
|
|
|
|
struct cubic
|
|
{
|
|
glm::dvec3 p0, p1, p2, p3;
|
|
|
|
explicit cubic(segment_data const &Piece)
|
|
{
|
|
p0 = Piece.points[segment_data::point::start];
|
|
p3 = Piece.points[segment_data::point::end];
|
|
auto const &control1{Piece.points[segment_data::point::control1]};
|
|
auto const &control2{Piece.points[segment_data::point::control2]};
|
|
if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
|
|
{
|
|
p1 = glm::mix(p0, p3, 1.0 / 3.0);
|
|
p2 = glm::mix(p0, p3, 2.0 / 3.0);
|
|
}
|
|
else
|
|
{
|
|
p1 = p0 + control1;
|
|
p2 = p3 + control2;
|
|
}
|
|
}
|
|
glm::dvec3 point(double const T) const
|
|
{
|
|
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;
|
|
}
|
|
glm::dvec3 first(double const T) const
|
|
{
|
|
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);
|
|
}
|
|
};
|
|
|
|
double plan_distance(glm::dvec3 const &A, glm::dvec3 const &B)
|
|
{
|
|
return glm::length(glm::dvec2{B.x - A.x, B.z - A.z});
|
|
}
|
|
|
|
// the gauge changes from 26 m ahead of a curve or of a cant ramp (PKP PLK ST-T2), so the tracks beyond the ends are looked at this far
|
|
double const lead_reach{26.0};
|
|
|
|
segment_data turned_piece(segment_data const &Path)
|
|
{
|
|
segment_data result{Path};
|
|
result.points[segment_data::point::start] = Path.points[segment_data::point::end];
|
|
result.points[segment_data::point::end] = Path.points[segment_data::point::start];
|
|
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
|
|
result.points[segment_data::point::control2] = Path.points[segment_data::point::control1];
|
|
result.rolls = {-Path.rolls[1], -Path.rolls[0]};
|
|
return result;
|
|
}
|
|
|
|
bool same_points(segment_data const &A, segment_data const &B)
|
|
{
|
|
for (int i = 0; i < 4; ++i)
|
|
if (glm::distance(A.points[i], B.points[i]) > 1e-3)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
glm::dvec3 plan_heading(segment_data const &Piece, double const T)
|
|
{
|
|
auto const direction{cubic{Piece}.first(T)};
|
|
glm::dvec3 const plan{direction.x, 0.0, direction.z};
|
|
return glm::length(plan) > 1e-9 ? glm::normalize(plan) : glm::dvec3{0.0, 0.0, 1.0};
|
|
}
|
|
|
|
// paths going on from the end of the last of the pieces, each turned to lead away from it, up to the lead reach
|
|
std::vector<segment_data> paths_beyond(segment_data Last, std::vector<segment_data> const &Own)
|
|
{
|
|
std::vector<segment_data> result;
|
|
double length{0.0};
|
|
while (length < lead_reach && result.size() < 16)
|
|
{
|
|
auto const at{Last.points[segment_data::point::end]};
|
|
if (simulation::Region == nullptr || false == simulation::Region->point_inside(at))
|
|
break;
|
|
auto const &paths{simulation::Region->section(at).cell(at).m_directories.paths};
|
|
std::optional<segment_data> next;
|
|
for (auto *track : paths)
|
|
{
|
|
if (track == nullptr || track->m_editorremoved || (track->iCategoryFlag & 1) == 0)
|
|
continue;
|
|
for (auto const &path : track->m_paths)
|
|
for (auto const forward : {true, false})
|
|
{
|
|
auto const candidate{forward ? path : turned_piece(path)};
|
|
if (glm::distance(candidate.points[segment_data::point::start], at) > 0.01 || glm::dot(plan_heading(Last, 1.0), plan_heading(candidate, 0.0)) < 0.9)
|
|
continue;
|
|
auto const known = [&](segment_data const &Piece) { return same_points(Piece, candidate) || same_points(turned_piece(Piece), candidate); };
|
|
if (std::any_of(Own.begin(), Own.end(), known) || std::any_of(result.begin(), result.end(), known))
|
|
continue;
|
|
auto const straight{glm::dot(plan_heading(candidate, 0.0), plan_heading(candidate, 1.0))};
|
|
if (next.has_value() && straight <= glm::dot(plan_heading(*next, 0.0), plan_heading(*next, 1.0)))
|
|
continue;
|
|
next = candidate;
|
|
}
|
|
}
|
|
if (false == next.has_value())
|
|
break;
|
|
cubic const curve{*next};
|
|
for (int k = 1; k <= 8; ++k)
|
|
length += plan_distance(curve.point((k - 1) / 8.0), curve.point(k / 8.0));
|
|
result.push_back(*next);
|
|
Last = *next;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// triangles of the model which share the look, in the space of the model
|
|
struct model_part
|
|
{
|
|
material_handle material{null_handle};
|
|
lighting_data lighting;
|
|
bool translucent{false};
|
|
std::vector<world_vertex> vertices;
|
|
};
|
|
|
|
// without the parts lit at night (the glow, the pool of light on the ground), which the instances show only with the lights on
|
|
void gather_parts(TSubModel *Submodel, glm::dmat4 const &Transform, material_data const &Skins, std::array<TSubModel *, 8> const &Lit, std::vector<model_part> &Parts)
|
|
{
|
|
for (auto *submodel = Submodel; submodel != nullptr; submodel = submodel->Next)
|
|
{
|
|
if (std::find(Lit.begin(), Lit.end(), submodel) != Lit.end())
|
|
continue;
|
|
auto transform{Transform};
|
|
if ((submodel->iFlags & 0xC000) != 0 && submodel->GetMatrix() != nullptr)
|
|
{
|
|
transform = Transform * glm::dmat4(glm::make_mat4(submodel->GetMatrix()->readArray()));
|
|
}
|
|
// the nearest level of detail only, the others would be drawn over it
|
|
if (submodel->eType == GL_TRIANGLES && submodel->fSquareMinDist <= 0.f && (submodel->m_geometry.handle.bank != 0 || submodel->m_geometry.handle.chunk != 0))
|
|
{
|
|
auto material{submodel->m_material};
|
|
if (material < 0)
|
|
{
|
|
material = Skins.replacable_skins[std::clamp(-material, 1, 4)];
|
|
}
|
|
model_part part;
|
|
part.material = material;
|
|
part.lighting.ambient = submodel->f4Ambient;
|
|
part.lighting.diffuse = submodel->f4Diffuse;
|
|
part.lighting.specular = submodel->f4Specular;
|
|
part.translucent = material != null_handle && GfxRenderer->Material(material)->get_or_guess_opacity() == 0.0f;
|
|
auto const normals{glm::transpose(glm::inverse(glm::dmat3(transform)))};
|
|
auto const &vertices{GfxRenderer->Vertices(submodel->m_geometry.handle)};
|
|
auto const &indices{GfxRenderer->Indices(submodel->m_geometry.handle)};
|
|
auto const take = [&](gfx::basic_vertex const &Vertex) {
|
|
auto vertex{Vertex.to_world()};
|
|
vertex.position = glm::dvec3(transform * glm::dvec4(vertex.position, 1.0));
|
|
auto const normal{normals * glm::dvec3(vertex.normal)};
|
|
vertex.normal = glm::length(normal) > 1e-9 ? glm::vec3(glm::normalize(normal)) : vertex.normal;
|
|
part.vertices.push_back(vertex);
|
|
};
|
|
if (false == indices.empty())
|
|
{
|
|
for (std::size_t i = 0; i + 2 < indices.size(); i += 3)
|
|
{
|
|
take(vertices[indices[i]]);
|
|
take(vertices[indices[i + 1]]);
|
|
take(vertices[indices[i + 2]]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (std::size_t i = 0; i + 2 < vertices.size(); i += 3)
|
|
{
|
|
take(vertices[i]);
|
|
take(vertices[i + 1]);
|
|
take(vertices[i + 2]);
|
|
}
|
|
}
|
|
if (false == part.vertices.empty())
|
|
{
|
|
Parts.push_back(std::move(part));
|
|
}
|
|
}
|
|
if (submodel->Child != nullptr)
|
|
{
|
|
gather_parts(submodel->Child, transform, Skins, Lit, Parts);
|
|
}
|
|
}
|
|
}
|
|
|
|
world_vertex blend(world_vertex const &A, world_vertex const &B, double const T)
|
|
{
|
|
world_vertex result;
|
|
result.position = glm::mix(A.position, B.position, T);
|
|
auto const normal{glm::mix(A.normal, B.normal, static_cast<float>(T))};
|
|
result.normal = glm::length(normal) > 1e-6f ? glm::normalize(normal) : A.normal;
|
|
result.texture = glm::mix(A.texture, B.texture, static_cast<float>(T));
|
|
return result;
|
|
}
|
|
|
|
// the part of the polygon on one side of the plane Along = Cut: Side 1 keeps what's beyond it, -1 what's before it
|
|
std::vector<world_vertex> clip(std::vector<world_vertex> const &Polygon, std::function<double(glm::dvec3 const &)> const &Along, double const Cut, double const Side)
|
|
{
|
|
std::vector<world_vertex> result;
|
|
for (std::size_t i = 0; i < Polygon.size(); ++i)
|
|
{
|
|
auto const ¤t{Polygon[i]};
|
|
auto const &next{Polygon[(i + 1) % Polygon.size()]};
|
|
auto const a{Side * (Along(current.position) - Cut)};
|
|
auto const b{Side * (Along(next.position) - Cut)};
|
|
if (a >= 0.0)
|
|
result.push_back(current);
|
|
if ((a >= 0.0) != (b >= 0.0))
|
|
result.push_back(blend(current, next, a / (a - b)));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// triangles cut across the axis of the model into slices, so a long face bends with the curve instead of spanning it straight
|
|
std::vector<world_vertex> slices(std::vector<world_vertex> const &Vertices, std::function<double(glm::dvec3 const &)> const &Along, double const Low, double const Width)
|
|
{
|
|
std::vector<world_vertex> result;
|
|
result.reserve(Vertices.size());
|
|
for (std::size_t i = 0; i + 2 < Vertices.size(); i += 3)
|
|
{
|
|
auto const a0{Along(Vertices[i].position)};
|
|
auto const a1{Along(Vertices[i + 1].position)};
|
|
auto const a2{Along(Vertices[i + 2].position)};
|
|
auto const first{static_cast<int>(std::floor((std::min({a0, a1, a2}) - Low) / Width))};
|
|
auto const last{static_cast<int>(std::ceil((std::max({a0, a1, a2}) - Low) / Width)) - 1};
|
|
if (last <= first)
|
|
{
|
|
result.insert(result.end(), Vertices.begin() + i, Vertices.begin() + i + 3);
|
|
continue;
|
|
}
|
|
std::vector<world_vertex> const triangle{Vertices[i], Vertices[i + 1], Vertices[i + 2]};
|
|
for (int slice = first; slice <= last; ++slice)
|
|
{
|
|
auto const polygon{clip(clip(triangle, Along, Low + slice * Width, 1.0), Along, Low + (slice + 1) * Width, -1.0)};
|
|
for (std::size_t k = 1; k + 1 < polygon.size(); ++k)
|
|
{
|
|
result.push_back(polygon[0]);
|
|
result.push_back(polygon[k]);
|
|
result.push_back(polygon[k + 1]);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct laid_model
|
|
{
|
|
std::vector<model_part> parts;
|
|
glm::dvec3 origin{0.0};
|
|
glm::dvec3 low{std::numeric_limits<double>::max()};
|
|
glm::dvec3 high{-std::numeric_limits<double>::max()};
|
|
bool bent{false};
|
|
};
|
|
|
|
struct layout
|
|
{
|
|
std::vector<laid_model> models;
|
|
glm::dvec3 low{std::numeric_limits<double>::max()};
|
|
glm::dvec3 high{-std::numeric_limits<double>::max()};
|
|
glm::dvec3 shift{0.0};
|
|
bool edge_far{false};
|
|
};
|
|
|
|
bool lay_out(sweep_node const &Sweep, layout &Layout)
|
|
{
|
|
auto const &definition{Sweep.definition()};
|
|
auto const scaling{glm::scale(glm::dmat4(1.0), definition.scale)};
|
|
for (auto const &entry : Sweep.items())
|
|
{
|
|
laid_model model;
|
|
auto const transform{scaling * entry.transform};
|
|
gather_parts(entry.model->Root, transform, entry.skins, entry.model->instance_parts().lights_on, model.parts);
|
|
model.origin = glm::dvec3(transform[3]);
|
|
if (false == model.parts.empty())
|
|
Layout.models.push_back(std::move(model));
|
|
}
|
|
if (Layout.models.empty())
|
|
return false;
|
|
auto &low{Layout.low};
|
|
auto &high{Layout.high};
|
|
for (auto &model : Layout.models)
|
|
{
|
|
for (auto const &part : model.parts)
|
|
for (auto const &vertex : part.vertices)
|
|
{
|
|
auto const local{Sweep.axes(vertex.position)};
|
|
model.low = glm::min(model.low, local);
|
|
model.high = glm::max(model.high, local);
|
|
}
|
|
low = glm::min(low, model.low);
|
|
high = glm::max(high, model.high);
|
|
}
|
|
for (auto &model : Layout.models)
|
|
model.bent = definition.bend && (Layout.models.size() == 1 || model.high.x - model.low.x >= 0.5 * (high.x - low.x));
|
|
if (std::any_of(Layout.models.begin(), Layout.models.end(), [](laid_model const &Model) { return Model.bent; }))
|
|
{
|
|
low = glm::dvec3{std::numeric_limits<double>::max()};
|
|
high = glm::dvec3{-std::numeric_limits<double>::max()};
|
|
for (auto const &model : Layout.models)
|
|
if (model.bent)
|
|
{
|
|
low = glm::min(low, model.low);
|
|
high = glm::max(high, model.high);
|
|
}
|
|
}
|
|
auto const nearside{definition.lateral >= 0.0 ? low.y : high.y};
|
|
auto const farside{definition.lateral >= 0.0 ? high.y : low.y};
|
|
double edgetop{-std::numeric_limits<double>::max()};
|
|
double nearbottom{std::numeric_limits<double>::max()}, farbottom{std::numeric_limits<double>::max()};
|
|
for (auto const &model : Layout.models)
|
|
if (model.bent || false == definition.bend)
|
|
for (auto const &part : model.parts)
|
|
for (auto const &vertex : part.vertices)
|
|
{
|
|
auto const local{Sweep.axes(vertex.position)};
|
|
if (std::abs(local.y - nearside) <= edge_band)
|
|
edgetop = std::max(edgetop, local.z);
|
|
if (std::abs(local.y - nearside) <= overhang_band)
|
|
nearbottom = std::min(nearbottom, local.z);
|
|
if (std::abs(local.y - farside) <= overhang_band)
|
|
farbottom = std::min(farbottom, local.z);
|
|
}
|
|
if (edgetop == -std::numeric_limits<double>::max())
|
|
edgetop = high.z;
|
|
Layout.edge_far = farbottom > nearbottom + overhang_depth;
|
|
auto const point{Sweep.axes(definition.scale * definition.point)};
|
|
double const sides[]{0.0, -nearside, -farside, -0.5 * (low.y + high.y), -point.y};
|
|
double const heights[]{0.0, -low.z, -high.z, -edgetop, -point.z};
|
|
Layout.shift = glm::dvec3{0.0, sides[std::clamp(definition.side_anchor, 0, 4)], heights[std::clamp(definition.height_anchor, 0, 4)]};
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// height of the head of the rail on the side of the offset over the points of the track, less the height of the rail; 0 for no side
|
|
double sweep_node::rail_head(double const Roll, double const Lateral)
|
|
{
|
|
auto const roll{glm::radians(Roll)};
|
|
auto const raise{Global.bRollFix ? 0.75 * std::abs(std::sin(roll)) : 0.0};
|
|
if (Lateral == 0.0)
|
|
return raise;
|
|
return raise - (Lateral > 0.0 ? 1.0 : -1.0) * 0.75 * std::sin(roll);
|
|
}
|
|
|
|
bool sweep_node::state::operator==(state const &Other) const
|
|
{
|
|
if (model != Other.model || skin != Other.skin || bend != Other.bend || step != Other.step || from != Other.from || to != Other.to || lateral != Other.lateral || height != Other.height || along_x != Other.along_x ||
|
|
flip != Other.flip || mirror != Other.mirror || tilt != Other.tilt || face != Other.face || widen != Other.widen || platform != Other.platform || parameters != Other.parameters || side_anchor != Other.side_anchor || height_anchor != Other.height_anchor || point != Other.point || scale != Other.scale || pieces.size() != Other.pieces.size())
|
|
{
|
|
return false;
|
|
}
|
|
for (std::size_t i = 0; i < pieces.size(); ++i)
|
|
{
|
|
if (pieces[i].points != Other.pieces[i].points || pieces[i].rolls != Other.pieces[i].rolls)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
sweep_node::sweep_node(scene::node_data const &Nodedata) : basic_node(Nodedata) {}
|
|
|
|
void sweep_node::import(cParser &Input, glm::dvec3 const &Offset)
|
|
{
|
|
m_state = state{};
|
|
m_state.model = Input.getToken<std::string>();
|
|
m_state.skin = Input.getToken<std::string>();
|
|
auto token{Input.getToken<std::string>()};
|
|
while (false == token.empty() && token != "endsweep")
|
|
{
|
|
if (token == "mode")
|
|
{
|
|
m_state.bend = Input.getToken<std::string>() != "repeat";
|
|
}
|
|
else if (token == "step")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.step;
|
|
}
|
|
else if (token == "range")
|
|
{
|
|
Input.getTokens(2);
|
|
Input >> m_state.from >> m_state.to;
|
|
}
|
|
else if (token == "offset")
|
|
{
|
|
Input.getTokens(2);
|
|
Input >> m_state.lateral >> m_state.height;
|
|
}
|
|
else if (token == "axis")
|
|
{
|
|
m_state.along_x = Input.getToken<std::string>() == "x";
|
|
}
|
|
else if (token == "flip")
|
|
{
|
|
m_state.flip = true;
|
|
}
|
|
else if (token == "mirror")
|
|
{
|
|
m_state.mirror = true;
|
|
}
|
|
else if (token == "tilt")
|
|
{
|
|
m_state.tilt = true;
|
|
}
|
|
else if (token == "widen")
|
|
{
|
|
m_state.widen = true;
|
|
}
|
|
else if (token == "platform")
|
|
{
|
|
m_state.platform = true;
|
|
}
|
|
else if (token == "noface")
|
|
{
|
|
m_state.face = false;
|
|
}
|
|
else if (token == "parameters")
|
|
{
|
|
int count{0};
|
|
Input.getTokens();
|
|
Input >> count;
|
|
m_state.parameters.clear();
|
|
for (int i = 0; i < count; ++i)
|
|
m_state.parameters.push_back(Input.getToken<std::string>(false));
|
|
}
|
|
else if (token == "anchor")
|
|
{
|
|
auto const side{Input.getToken<std::string>()};
|
|
auto const height{Input.getToken<std::string>()};
|
|
m_state.side_anchor = side == "near" ? 1 : side == "far" ? 2 : side == "centre" ? 3 : side == "point" ? 4 : 0;
|
|
m_state.height_anchor = height == "bottom" ? 1 : height == "top" ? 2 : height == "edge" ? 3 : height == "point" ? 4 : 0;
|
|
}
|
|
else if (token == "point")
|
|
{
|
|
Input.getTokens(3);
|
|
Input >> m_state.point.x >> m_state.point.y >> m_state.point.z;
|
|
}
|
|
else if (token == "scale")
|
|
{
|
|
Input.getTokens(3);
|
|
Input >> m_state.scale.x >> m_state.scale.y >> m_state.scale.z;
|
|
}
|
|
else if (token == "piece")
|
|
{
|
|
segment_data piece;
|
|
piece.deserialize(Input, Offset);
|
|
m_state.pieces.push_back(piece);
|
|
}
|
|
else
|
|
{
|
|
ErrorLog("Bad sweep: unknown property \"" + token + "\" of \"" + m_name + "\" in file \"" + Input.Name() + "\"");
|
|
}
|
|
token = Input.getToken<std::string>();
|
|
}
|
|
rebuild_samples();
|
|
}
|
|
|
|
void sweep_node::define(state const &State)
|
|
{
|
|
auto const shown{m_shapes.shown()};
|
|
if (shown)
|
|
{
|
|
hide();
|
|
}
|
|
m_state = State;
|
|
rebuild_samples();
|
|
if (shown)
|
|
{
|
|
show();
|
|
}
|
|
}
|
|
|
|
void sweep_node::sample_piece(segment_data const &Piece, std::vector<sample> &Samples, double &Station)
|
|
{
|
|
cubic const curve{Piece};
|
|
auto const count{std::clamp(static_cast<int>(plan_distance(curve.p0, curve.p3) / sample_spacing), 4, 4000)};
|
|
for (int k = Samples.empty() ? 0 : 1; k <= count; ++k)
|
|
{
|
|
auto const t{static_cast<double>(k) / count};
|
|
auto const position{curve.point(t)};
|
|
if (false == Samples.empty())
|
|
{
|
|
Station += plan_distance(Samples.back().position, position);
|
|
}
|
|
auto tangent{curve.first(t)};
|
|
tangent = glm::length(tangent) > 1e-9 ? glm::normalize(tangent) : glm::dvec3{0.0, 0.0, 1.0};
|
|
Samples.push_back({Station, position, tangent, Piece.rolls[0] + (Piece.rolls[1] - Piece.rolls[0]) * t, {}, {}});
|
|
}
|
|
}
|
|
|
|
// the structure gauge widens ahead of the curves and of the cant, the same way the gauge check of the editor has it
|
|
void sweep_node::gauge_along(std::vector<sample> &Samples)
|
|
{
|
|
if (Samples.size() < 3)
|
|
return;
|
|
std::vector<double> chainage, curvature, cant;
|
|
for (std::size_t i = 0; i < Samples.size(); ++i)
|
|
{
|
|
auto const &before{Samples[i == 0 ? 0 : i - 1]};
|
|
auto const &after{Samples[std::min(i + 1, Samples.size() - 1)]};
|
|
glm::dvec2 const a{before.tangent.x, before.tangent.z};
|
|
glm::dvec2 const b{after.tangent.x, after.tangent.z};
|
|
auto const span{after.station - before.station};
|
|
auto const turn{glm::length(a) > 1e-9 && glm::length(b) > 1e-9 ? std::atan2(a.x * b.y - a.y * b.x, glm::dot(a, b)) : 0.0};
|
|
chainage.push_back(Samples[i].station);
|
|
curvature.push_back(span > 1e-9 ? turn / span : 0.0);
|
|
cant.push_back(1.5 * std::abs(std::sin(glm::radians(Samples[i].roll))));
|
|
}
|
|
auto const sections{gauge::sections(gauge::kind::unified, chainage, curvature, cant)};
|
|
for (std::size_t i = 0; i < Samples.size() && i < sections.size(); ++i)
|
|
{
|
|
Samples[i].widening = sections[i].lower;
|
|
Samples[i].cant = sections[i].cant;
|
|
}
|
|
}
|
|
|
|
void sweep_node::beside_switches(std::vector<sample> &Samples)
|
|
{
|
|
if (Samples.size() < 3)
|
|
return;
|
|
auto const side{m_state.lateral >= 0.0 ? 1 : 0};
|
|
auto const sign{m_state.lateral >= 0.0 ? 1.0 : -1.0};
|
|
auto const edgeheight{std::max(0.0, m_state.height - gauge::rail_height)};
|
|
auto const total = [&](sample const &Sample) { return Sample.widening[side] + std::max(0.0, Sample.cant[side]) * edgeheight / 1.5; };
|
|
auto const plan = [](glm::dvec3 const &Vector) {
|
|
glm::dvec3 const flat{Vector.x, 0.0, Vector.z};
|
|
return glm::length(flat) > 1e-9 ? glm::normalize(flat) : glm::dvec3{0.0, 0.0, 1.0};
|
|
};
|
|
glm::dvec3 low{std::numeric_limits<double>::max()}, high{-std::numeric_limits<double>::max()};
|
|
for (auto const &sample : Samples)
|
|
{
|
|
low = glm::min(low, sample.position);
|
|
high = glm::max(high, sample.position);
|
|
}
|
|
for (auto *track : simulation::Paths.sequence())
|
|
{
|
|
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.size() < 2)
|
|
continue;
|
|
auto const points{track->m_paths.front().points[segment_data::point::start]};
|
|
if (points.x < low.x - 1.0 || points.x > high.x + 1.0 || points.z < low.z - 1.0 || points.z > high.z + 1.0)
|
|
continue;
|
|
std::size_t at{0};
|
|
auto best{std::numeric_limits<double>::max()};
|
|
for (std::size_t i = 0; i < Samples.size(); ++i)
|
|
if (auto const distance{plan_distance(Samples[i].position, points)}; distance < best)
|
|
{
|
|
best = distance;
|
|
at = i;
|
|
}
|
|
if (best > sample_spacing)
|
|
continue;
|
|
auto const &closest_sample{Samples[at]};
|
|
auto const along{plan(closest_sample.tangent)};
|
|
glm::dvec3 const toward{points.x - closest_sample.position.x, 0.0, points.z - closest_sample.position.z};
|
|
if (std::abs(glm::dot(toward, glm::dvec3{-along.z, 0.0, along.x})) > 0.05)
|
|
continue;
|
|
auto const s0{closest_sample.station + glm::dot(toward, along)};
|
|
for (auto const &path : track->m_paths)
|
|
{
|
|
auto const heading{glm::dot(plan_heading(path, 0.0), along)};
|
|
if (std::abs(heading) < 0.9)
|
|
continue;
|
|
auto const beyond{paths_beyond(path, {path})};
|
|
std::vector<sample> chain;
|
|
double station{0.0};
|
|
if (heading > 0.0)
|
|
{
|
|
for (auto const &sample : Samples)
|
|
if (sample.station >= s0 - outer_reach && sample.station <= s0 + 1e-3)
|
|
{
|
|
station = sample.station;
|
|
chain.push_back(sample);
|
|
}
|
|
sample_piece(path, chain, station);
|
|
for (auto const &piece : beyond)
|
|
sample_piece(piece, chain, station);
|
|
}
|
|
else
|
|
{
|
|
for (auto piece = beyond.rbegin(); piece != beyond.rend(); ++piece)
|
|
sample_piece(turned_piece(*piece), chain, station);
|
|
sample_piece(turned_piece(path), chain, station);
|
|
auto const shift{station - s0};
|
|
for (auto const &sample : Samples)
|
|
if (sample.station > s0 + 1e-3 && sample.station <= s0 + outer_reach)
|
|
{
|
|
chain.push_back(sample);
|
|
chain.back().station += shift;
|
|
}
|
|
}
|
|
gauge_along(chain);
|
|
for (auto &here : Samples)
|
|
{
|
|
if (plan_distance(here.position, points) > outer_reach + 2.0 * lead_reach + 100.0)
|
|
continue;
|
|
auto const forward{plan(here.tangent)};
|
|
sample const *nearest{nullptr};
|
|
auto closest{std::numeric_limits<double>::max()};
|
|
for (auto const &point : chain)
|
|
{
|
|
auto const gap{std::abs(glm::dot(glm::dvec3{point.position.x - here.position.x, 0.0, point.position.z - here.position.z}, forward))};
|
|
if (gap < closest)
|
|
{
|
|
closest = gap;
|
|
nearest = &point;
|
|
}
|
|
}
|
|
if (nearest == nullptr || closest > 1.5 * sample_spacing)
|
|
continue;
|
|
auto const offset{sign * glm::dot(glm::dvec3{nearest->position.x - here.position.x, 0.0, nearest->position.z - here.position.z}, glm::dvec3{-forward.z, 0.0, forward.x})};
|
|
if (offset > 0.05)
|
|
continue;
|
|
auto const needed{offset + total(*nearest)};
|
|
if (needed > total(here))
|
|
here.widening[side] = needed - std::max(0.0, here.cant[side]) * edgeheight / 1.5;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool sweep_node::bridge()
|
|
{
|
|
auto const turned = [](segment_data const &Path) {
|
|
segment_data result{Path};
|
|
result.points[segment_data::point::start] = Path.points[segment_data::point::end];
|
|
result.points[segment_data::point::end] = Path.points[segment_data::point::start];
|
|
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
|
|
result.points[segment_data::point::control2] = Path.points[segment_data::point::control1];
|
|
result.rolls = {-Path.rolls[1], -Path.rolls[0]};
|
|
return result;
|
|
};
|
|
auto const same = [](segment_data const &A, segment_data const &B) {
|
|
for (int i = 0; i < 4; ++i)
|
|
if (glm::distance(A.points[i], B.points[i]) > 1e-3)
|
|
return false;
|
|
return true;
|
|
};
|
|
auto const length = [](segment_data const &Piece) {
|
|
std::vector<sample> samples;
|
|
double station{0.0};
|
|
sample_piece(Piece, samples, station);
|
|
return station;
|
|
};
|
|
auto const heading = [](segment_data const &Piece, double const T) {
|
|
auto const direction{cubic{Piece}.first(T)};
|
|
glm::dvec3 const plan{direction.x, 0.0, direction.z};
|
|
return glm::length(plan) > 1e-9 ? glm::normalize(plan) : glm::dvec3{0.0, 0.0, 1.0};
|
|
};
|
|
auto state{m_state};
|
|
auto changed{false};
|
|
double station{0.0};
|
|
for (std::size_t i = 0; i + 1 < state.pieces.size(); ++i)
|
|
{
|
|
station += length(state.pieces[i]);
|
|
auto const target{state.pieces[i + 1].points[segment_data::point::start]};
|
|
auto at{state.pieces[i].points[segment_data::point::end]};
|
|
if (glm::distance(at, target) < 0.01)
|
|
continue;
|
|
auto const chord{plan_distance(at, target)};
|
|
std::vector<segment_data> fill;
|
|
auto previous{state.pieces[i]};
|
|
auto reached{false};
|
|
for (int step = 0; step < 20 && false == reached; ++step)
|
|
{
|
|
std::optional<segment_data> best;
|
|
auto bestdistance{std::numeric_limits<double>::max()};
|
|
for (auto *track : simulation::Paths.sequence())
|
|
{
|
|
if (track == nullptr || track->m_editorremoved)
|
|
continue;
|
|
for (auto const &path : track->m_paths)
|
|
for (auto const forward : {true, false})
|
|
{
|
|
auto const candidate{forward ? path : turned(path)};
|
|
if (glm::distance(candidate.points[segment_data::point::start], at) > 0.01 || same(candidate, previous) || same(turned(candidate), previous))
|
|
continue;
|
|
if (glm::dot(heading(previous, 1.0), heading(candidate, 0.0)) < 0.5)
|
|
continue;
|
|
auto const distance{glm::distance(candidate.points[segment_data::point::end], target)};
|
|
if (distance < bestdistance)
|
|
{
|
|
bestdistance = distance;
|
|
best = candidate;
|
|
}
|
|
}
|
|
}
|
|
if (false == best.has_value())
|
|
break;
|
|
fill.push_back(*best);
|
|
previous = *best;
|
|
at = best->points[segment_data::point::end];
|
|
reached = glm::distance(at, target) < 0.01;
|
|
}
|
|
if (false == reached)
|
|
continue;
|
|
double added{0.0};
|
|
for (auto const &piece : fill)
|
|
added += length(piece);
|
|
auto const shift{added - chord};
|
|
if (state.from >= station)
|
|
state.from += shift;
|
|
if (state.to >= station)
|
|
state.to += shift;
|
|
state.pieces.insert(state.pieces.begin() + i + 1, fill.begin(), fill.end());
|
|
station += added;
|
|
i += fill.size();
|
|
changed = true;
|
|
}
|
|
if (changed)
|
|
{
|
|
WriteLog("Sweep \"" + m_name + "\": the gaps between its pieces filled with the paths which join them");
|
|
define(state);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
void sweep_node::refresh()
|
|
{
|
|
auto const current{m_state};
|
|
define(current);
|
|
}
|
|
|
|
void sweep_node::rebuild_samples()
|
|
{
|
|
m_boundsvalid = false;
|
|
m_samples.clear();
|
|
std::vector<segment_data> before, after;
|
|
if ((m_state.widen || m_state.platform) && false == m_state.pieces.empty())
|
|
{
|
|
before = paths_beyond(turned_piece(m_state.pieces.front()), m_state.pieces);
|
|
after = paths_beyond(m_state.pieces.back(), m_state.pieces);
|
|
}
|
|
double station{0.0};
|
|
for (auto piece = before.rbegin(); piece != before.rend(); ++piece)
|
|
sample_piece(turned_piece(*piece), m_samples, station);
|
|
auto const head{station};
|
|
auto const first{m_samples.empty() ? std::size_t{0} : m_samples.size() - 1};
|
|
for (auto const &piece : m_state.pieces)
|
|
sample_piece(piece, m_samples, station);
|
|
auto const last{m_samples.size()};
|
|
for (auto const &piece : after)
|
|
sample_piece(piece, m_samples, station);
|
|
gauge_along(m_samples);
|
|
if (m_state.widen || m_state.platform)
|
|
beside_switches(m_samples);
|
|
// the paths beyond the ends only lead the gauge in, the copies stay on the pieces
|
|
m_samples.erase(m_samples.begin() + last, m_samples.end());
|
|
m_samples.erase(m_samples.begin(), m_samples.begin() + first);
|
|
for (auto &sample : m_samples)
|
|
sample.station -= head;
|
|
m_distances.clear();
|
|
glm::dvec3 previous{0.0};
|
|
for (auto const &sample : m_samples)
|
|
{
|
|
frame at;
|
|
at.widening = sample.widening;
|
|
at.cant = sample.cant;
|
|
glm::dvec3 left{-sample.tangent.z, 0.0, sample.tangent.x};
|
|
left = glm::length(left) > 1e-9 ? glm::normalize(left) : glm::dvec3{-1.0, 0.0, 0.0};
|
|
auto const point{sample.position + left * (m_state.lateral + setback(at))};
|
|
m_distances.push_back(m_distances.empty() ? 0.0 : m_distances.back() + std::max(1e-6, plan_distance(previous, point)));
|
|
previous = point;
|
|
}
|
|
if (false == m_samples.empty())
|
|
{
|
|
location(frame_at(0.5 * length()).position);
|
|
}
|
|
m_area.radius = -1.f;
|
|
}
|
|
|
|
double sweep_node::length() const
|
|
{
|
|
return m_samples.empty() ? 0.0 : m_samples.back().station;
|
|
}
|
|
|
|
double sweep_node::start() const
|
|
{
|
|
return m_state.from;
|
|
}
|
|
|
|
double sweep_node::end() const
|
|
{
|
|
return m_state.to < 0.0 ? length() : std::max(m_state.to, start());
|
|
}
|
|
|
|
double sweep_node::distance_at(double const Station) const
|
|
{
|
|
if (m_samples.size() < 2)
|
|
return Station;
|
|
if (Station <= m_samples.front().station)
|
|
return m_distances.front() + Station - m_samples.front().station;
|
|
if (Station >= m_samples.back().station)
|
|
return m_distances.back() + Station - m_samples.back().station;
|
|
auto const after{static_cast<std::size_t>(std::upper_bound(m_samples.begin(), m_samples.end(), Station, [](double const Value, sample const &Sample) { return Value < Sample.station; }) - m_samples.begin())};
|
|
auto const before{after - 1};
|
|
auto const span{m_samples[after].station - m_samples[before].station};
|
|
auto const f{span > 1e-9 ? (Station - m_samples[before].station) / span : 0.0};
|
|
return m_distances[before] + (m_distances[after] - m_distances[before]) * f;
|
|
}
|
|
|
|
double sweep_node::station_at(double const Distance) const
|
|
{
|
|
if (m_samples.size() < 2)
|
|
return Distance;
|
|
if (Distance <= m_distances.front())
|
|
return m_samples.front().station + Distance - m_distances.front();
|
|
if (Distance >= m_distances.back())
|
|
return m_samples.back().station + Distance - m_distances.back();
|
|
auto const after{static_cast<std::size_t>(std::upper_bound(m_distances.begin(), m_distances.end(), Distance) - m_distances.begin())};
|
|
auto const before{after - 1};
|
|
auto const span{m_distances[after] - m_distances[before]};
|
|
auto const f{span > 1e-12 ? (Distance - m_distances[before]) / span : 0.0};
|
|
return m_samples[before].station + (m_samples[after].station - m_samples[before].station) * f;
|
|
}
|
|
|
|
double sweep_node::setback(frame const &At) const
|
|
{
|
|
if (false == m_state.widen && false == m_state.platform)
|
|
return 0.0;
|
|
auto const side{m_state.lateral >= 0.0 ? 1 : 0};
|
|
auto const edgeheight{std::max(0.0, m_state.height - gauge::rail_height)};
|
|
auto const widening{At.widening[side] + std::max(0.0, At.cant[side]) * edgeheight / 1.5};
|
|
return m_state.lateral >= 0.0 ? widening : -widening;
|
|
}
|
|
|
|
double sweep_node::rise(frame const &At) const
|
|
{
|
|
if (m_state.tilt)
|
|
return rail_head(At.roll, 0.0);
|
|
if (false == m_state.platform)
|
|
return rail_head(At.roll, m_state.lateral);
|
|
// PKP PLK ST-T2 table 3: H = Hi + D/2 -+ D (XB + dbS) / 1500 over the lower rail, with the cant D of the gauge, which
|
|
// changes from 20 m (inner side) and 26 m (outer side) ahead of the cant ramp, as the platform has to. taken as the
|
|
// turn of the edge with the plane of the rail heads, as the table has it only to the first order, and on the outer
|
|
// side, where the edge stays at XB + dbS instead of XB + dbS - dbD, the plane is higher by dbD D / 1500 there
|
|
auto const side{m_state.lateral >= 0.0 ? 1 : 0};
|
|
auto const cant{At.cant[side]};
|
|
auto const lower{rail_head(At.roll, 0.0) - 0.75 * std::abs(std::sin(glm::radians(At.roll)))};
|
|
auto const edgeheight{std::max(0.0, m_state.height - gauge::rail_height)};
|
|
auto const slope{std::clamp(cant / 1.5, -0.5, 0.5)};
|
|
auto const level{std::sqrt(1.0 - slope * slope)};
|
|
auto const across{std::abs(m_state.lateral) + std::abs(setback(At))};
|
|
return lower + 0.5 * std::abs(cant) - (across - edgeheight * slope) * slope / level + edgeheight * (level - 1.0);
|
|
}
|
|
|
|
glm::dvec3 sweep_node::axes(glm::dvec3 const &Vector) const
|
|
{
|
|
auto const &definition{m_state};
|
|
auto const needsflip{definition.along_x ? definition.lateral < 0.0 : definition.lateral > 0.0};
|
|
auto const flip{definition.flip != (definition.face && needsflip)};
|
|
glm::dvec3 result{definition.along_x ? Vector.x : Vector.z, definition.along_x ? Vector.z : -Vector.x, Vector.y};
|
|
if (flip)
|
|
{
|
|
result.x = -result.x;
|
|
result.y = -result.y;
|
|
}
|
|
if (definition.mirror)
|
|
{
|
|
result.y = -result.y;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool sweep_node::bounds(glm::dvec3 &Low, glm::dvec3 &High, glm::dvec3 &Shift) const
|
|
{
|
|
if (false == m_boundsvalid)
|
|
{
|
|
layout laid;
|
|
m_boundsfound = lay_out(*this, laid);
|
|
m_low = laid.low;
|
|
m_high = laid.high;
|
|
m_shift = laid.shift;
|
|
m_edgefar = laid.edge_far;
|
|
m_boundsvalid = true;
|
|
}
|
|
Low = m_low;
|
|
High = m_high;
|
|
Shift = m_shift;
|
|
return m_boundsfound;
|
|
}
|
|
|
|
bool sweep_node::edge_far() const
|
|
{
|
|
glm::dvec3 low, high, shift;
|
|
return bounds(low, high, shift) && m_edgefar;
|
|
}
|
|
|
|
sweep_node::frame sweep_node::frame_at(double const Station) const
|
|
{
|
|
frame result;
|
|
if (m_samples.empty())
|
|
{
|
|
return result;
|
|
}
|
|
auto const found{std::upper_bound(m_samples.begin(), m_samples.end(), Station, [](double const Value, sample const &Sample) { return Value < Sample.station; })};
|
|
auto const after{found == m_samples.end() ? std::prev(found) : found};
|
|
auto const before{after == m_samples.begin() ? after : std::prev(after)};
|
|
auto const span{after->station - before->station};
|
|
auto const f{span > 1e-9 ? std::clamp((Station - before->station) / span, 0.0, 1.0) : 0.0};
|
|
result.position = glm::mix(before->position, after->position, f);
|
|
// beyond the ends the curve goes on straight
|
|
if (Station < m_samples.front().station)
|
|
{
|
|
result.position = m_samples.front().position + m_samples.front().tangent * (Station - m_samples.front().station);
|
|
}
|
|
else if (Station > m_samples.back().station)
|
|
{
|
|
result.position = m_samples.back().position + m_samples.back().tangent * (Station - m_samples.back().station);
|
|
}
|
|
auto forward{glm::mix(before->tangent, after->tangent, f)};
|
|
forward = glm::length(forward) > 1e-9 ? glm::normalize(forward) : glm::dvec3{0.0, 0.0, 1.0};
|
|
glm::dvec3 left{-forward.z, 0.0, forward.x};
|
|
left = glm::length(left) > 1e-9 ? glm::normalize(left) : glm::dvec3{-1.0, 0.0, 0.0};
|
|
glm::dvec3 up{glm::normalize(glm::cross(left, forward))};
|
|
result.roll = before->roll + (after->roll - before->roll) * f;
|
|
for (int side = 0; side < 2; ++side)
|
|
{
|
|
result.widening[side] = before->widening[side] + (after->widening[side] - before->widening[side]) * f;
|
|
result.cant[side] = before->cant[side] + (after->cant[side] - before->cant[side]) * f;
|
|
}
|
|
if (m_state.tilt)
|
|
{
|
|
auto const roll{glm::radians(result.roll)};
|
|
auto const turnedleft{left * std::cos(roll) - up * std::sin(roll)};
|
|
up = up * std::cos(roll) + left * std::sin(roll);
|
|
left = turnedleft;
|
|
}
|
|
result.forward = forward;
|
|
result.left = left;
|
|
result.up = up;
|
|
return result;
|
|
}
|
|
|
|
// models a template is made of, with their placement in the space of the template
|
|
std::vector<sweep_node::item> sweep_node::items() const
|
|
{
|
|
std::vector<item> result;
|
|
auto const &definition{m_state};
|
|
auto const ends = [](std::string const &Name, std::string const &Extension) { return Name.size() >= Extension.size() && Name.compare(Name.size() - Extension.size(), Extension.size(), Extension) == 0; };
|
|
auto name{definition.model};
|
|
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char const Character) { return static_cast<char>(std::tolower(Character)); });
|
|
if (false == ends(name, ".inc"))
|
|
{
|
|
item single;
|
|
single.model = TModelsManager::GetModel(definition.model, false, false);
|
|
if (false == definition.skin.empty() && definition.skin != "none")
|
|
single.skins.assign(definition.skin);
|
|
if (single.model != nullptr)
|
|
result.push_back(single);
|
|
return result;
|
|
}
|
|
cParser parser(definition.model, cParser::buffer_FILE, Global.asCurrentSceneryPath, Global.bLoadTraction, definition.parameters);
|
|
std::vector<glm::dvec3> offsets{glm::dvec3{0.0}};
|
|
glm::dvec3 rotation{0.0};
|
|
auto token{parser.getToken<std::string>()};
|
|
while (false == token.empty())
|
|
{
|
|
if (token == "origin")
|
|
{
|
|
glm::dvec3 offset;
|
|
parser.getTokens(3);
|
|
parser >> offset.x >> offset.y >> offset.z;
|
|
offsets.push_back(offsets.back() + offset);
|
|
}
|
|
else if (token == "endorigin")
|
|
{
|
|
if (offsets.size() > 1)
|
|
offsets.pop_back();
|
|
}
|
|
else if (token == "rotate")
|
|
{
|
|
parser.getTokens(3);
|
|
parser >> rotation.x >> rotation.y >> rotation.z;
|
|
}
|
|
else if (token == "node")
|
|
{
|
|
double rangemax, rangemin;
|
|
std::string nodename, type;
|
|
parser.getTokens(4);
|
|
parser >> rangemax >> rangemin >> nodename >> type;
|
|
if (type == "model")
|
|
{
|
|
glm::dvec3 location;
|
|
double yaw;
|
|
parser.getTokens(4);
|
|
parser >> location.x >> location.y >> location.z >> yaw;
|
|
auto const model{parser.getToken<std::string>()};
|
|
auto const skin{parser.getToken<std::string>()};
|
|
auto rest{parser.getToken<std::string>()};
|
|
while (false == rest.empty() && rest != "endmodel")
|
|
rest = parser.getToken<std::string>();
|
|
item entry;
|
|
entry.model = TModelsManager::GetModel(model, false, false);
|
|
if (skin != "none" && false == skin.empty() && skin.front() != '*')
|
|
entry.skins.assign(skin);
|
|
// placed the way the scenery places a model in an origin turned by a rotate statement
|
|
auto const place{glm::rotateY(location, glm::radians(rotation.y)) + offsets.back()};
|
|
glm::dmat4 transform{glm::translate(glm::dmat4(1.0), place)};
|
|
transform = glm::rotate(transform, glm::radians(rotation.y + yaw), glm::dvec3{0.0, 1.0, 0.0});
|
|
transform = glm::rotate(transform, glm::radians(rotation.x), glm::dvec3{1.0, 0.0, 0.0});
|
|
transform = glm::rotate(transform, glm::radians(rotation.z), glm::dvec3{0.0, 0.0, 1.0});
|
|
entry.transform = transform;
|
|
if (entry.model != nullptr && rangemin >= 0.0)
|
|
result.push_back(entry);
|
|
}
|
|
else
|
|
{
|
|
// whatever else the template defines isn't laid along the curve
|
|
auto rest{parser.getToken<std::string>()};
|
|
while (false == rest.empty() && rest != "end" + type && rest != "end" && rest != "endtri" && rest != "endline")
|
|
rest = parser.getToken<std::string>();
|
|
}
|
|
}
|
|
token = parser.getToken<std::string>();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double sweep_node::project(glm::dvec3 const &Point, double &Lateral) const
|
|
{
|
|
double best{std::numeric_limits<double>::max()};
|
|
double station{0.0};
|
|
Lateral = 0.0;
|
|
glm::dvec2 const point{Point.x, Point.z};
|
|
for (std::size_t i = 1; i < m_samples.size(); ++i)
|
|
{
|
|
glm::dvec2 const a{m_samples[i - 1].position.x, m_samples[i - 1].position.z};
|
|
glm::dvec2 const b{m_samples[i].position.x, m_samples[i].position.z};
|
|
auto const ab{b - a};
|
|
auto const length2{glm::dot(ab, ab)};
|
|
if (length2 < 1e-12)
|
|
continue;
|
|
auto const t{std::clamp(glm::dot(point - a, ab) / length2, 0.0, 1.0)};
|
|
auto const distance{glm::length(point - (a + ab * t))};
|
|
if (distance < best)
|
|
{
|
|
best = distance;
|
|
station = m_samples[i - 1].station + (m_samples[i].station - m_samples[i - 1].station) * t;
|
|
auto const side{ab.x * (point - a).y - ab.y * (point - a).x};
|
|
Lateral = side >= 0.0 ? distance : -distance;
|
|
}
|
|
}
|
|
return station;
|
|
}
|
|
|
|
double sweep_node::model_length() const
|
|
{
|
|
auto const pieces{items()};
|
|
auto const scaling{glm::scale(glm::dmat4(1.0), m_state.scale)};
|
|
double low{std::numeric_limits<double>::max()}, high{-std::numeric_limits<double>::max()};
|
|
for (auto const &entry : pieces)
|
|
{
|
|
std::vector<model_part> parts;
|
|
gather_parts(entry.model->Root, scaling * entry.transform, entry.skins, entry.model->instance_parts().lights_on, parts);
|
|
for (auto const &part : parts)
|
|
for (auto const &vertex : part.vertices)
|
|
{
|
|
auto const along{m_state.along_x ? vertex.position.x : vertex.position.z};
|
|
low = std::min(low, along);
|
|
high = std::max(high, along);
|
|
}
|
|
}
|
|
return high > low ? high - low : 0.0;
|
|
}
|
|
|
|
std::vector<scene::shape_node> sweep_node::create_shapes() const
|
|
{
|
|
std::vector<scene::shape_node> shapes;
|
|
auto const &definition{m_state};
|
|
if (m_samples.size() < 2)
|
|
return shapes;
|
|
layout laid;
|
|
if (false == lay_out(*this, laid))
|
|
return shapes;
|
|
auto &pieces{laid.models};
|
|
auto const low{laid.low};
|
|
auto const high{laid.high};
|
|
auto const anchor_shift{laid.shift};
|
|
auto const own{high.x - low.x};
|
|
auto const first{distance_at(start())};
|
|
auto const stretch{distance_at(end()) - first};
|
|
if (own < 1e-3 || stretch < 1e-3)
|
|
return shapes;
|
|
if (definition.bend)
|
|
{
|
|
auto const along = [&](glm::dvec3 const &Position) { return axes(Position).x; };
|
|
for (auto &model : pieces)
|
|
if (model.bent)
|
|
for (auto &part : model.parts)
|
|
part.vertices = slices(part.vertices, along, low.x, slice_width);
|
|
}
|
|
auto const copy_length{definition.step > 0.01 ? definition.step : own};
|
|
// copies end to end fill the stretch exactly, each one squeezed or stretched a bit; repeated ones go at even steps
|
|
auto const copies{definition.bend ? std::max(1, static_cast<int>(std::round(stretch / copy_length))) : static_cast<int>(std::floor(stretch / copy_length + 1e-6)) + 1};
|
|
auto const spacing{definition.bend ? stretch / copies : copy_length};
|
|
auto const scale{definition.bend ? spacing / own : 1.0};
|
|
std::size_t percopy{0};
|
|
for (auto const &model : pieces)
|
|
for (auto const &part : model.parts)
|
|
percopy += part.vertices.size();
|
|
auto const limit{std::max<std::size_t>(1, vertex_limit / std::max<std::size_t>(1, percopy))};
|
|
if (static_cast<std::size_t>(copies) > limit)
|
|
ErrorLog("Bad sweep: \"" + m_name + "\" would make " + std::to_string(copies) + " copies of \"" + definition.model + "\", only " + std::to_string(limit) + " are made");
|
|
auto const count{std::min<std::size_t>(copies, limit)};
|
|
struct batch
|
|
{
|
|
material_handle material;
|
|
lighting_data lighting;
|
|
bool translucent;
|
|
std::vector<world_vertex> vertices;
|
|
};
|
|
std::vector<batch> batches;
|
|
auto const target = [&](model_part const &Part) -> std::vector<world_vertex> & {
|
|
for (auto &entry : batches)
|
|
if (entry.material == Part.material && entry.lighting == Part.lighting && entry.translucent == Part.translucent)
|
|
return entry.vertices;
|
|
batches.push_back({Part.material, Part.lighting, Part.translucent, {}});
|
|
return batches.back().vertices;
|
|
};
|
|
// heights are given from the head of the rail on the side of the copies. the cant turns the track about its inner rail when the
|
|
// simulation raises it for the cant, about the axis otherwise; a copy which leans with the cant goes up with the axis
|
|
auto const cone = [&](frame const &At) { return rise(At); };
|
|
auto const upright = [](frame At) {
|
|
glm::dvec3 forward{At.forward.x, 0.0, At.forward.z};
|
|
forward = glm::length(forward) > 1e-9 ? glm::normalize(forward) : glm::dvec3{0.0, 0.0, 1.0};
|
|
At.forward = forward;
|
|
At.left = glm::dvec3{-forward.z, 0.0, forward.x};
|
|
At.up = glm::dvec3{0.0, 1.0, 0.0};
|
|
return At;
|
|
};
|
|
glm::dvec3 const offset{0.0, definition.height, 0.0};
|
|
for (std::size_t copy = 0; copy < count; ++copy)
|
|
{
|
|
auto const base{first + spacing * static_cast<double>(copy)};
|
|
for (auto const &model : pieces)
|
|
{
|
|
// a model which doesn't bend stands upright at its point: the place of its origin along the curve
|
|
auto const anchor{axes(model.origin) + anchor_shift};
|
|
auto const stand{upright(frame_at(station_at(definition.bend ? base + (anchor.x - low.x) * scale : base)))};
|
|
for (auto const &part : model.parts)
|
|
{
|
|
auto &vertices{target(part)};
|
|
auto const first{vertices.size()};
|
|
for (auto const &vertex : part.vertices)
|
|
{
|
|
auto const local{axes(vertex.position) + anchor_shift};
|
|
auto const direction{axes(glm::dvec3(vertex.normal))};
|
|
world_vertex placed{vertex};
|
|
if (model.bent)
|
|
{
|
|
auto const at{frame_at(station_at(base + (local.x - low.x) * scale))};
|
|
placed.position = at.position + at.left * (local.y + definition.lateral + setback(at)) + at.up * local.z + offset + glm::dvec3{0.0, cone(at), 0.0};
|
|
placed.normal = glm::vec3(glm::normalize(at.forward * direction.x + at.left * direction.y + at.up * direction.z));
|
|
}
|
|
else
|
|
{
|
|
auto const along{definition.bend ? local.x - anchor.x : local.x};
|
|
placed.position = stand.position + stand.forward * along + stand.left * (local.y + definition.lateral + setback(stand)) + stand.up * local.z + offset + glm::dvec3{0.0, cone(stand), 0.0};
|
|
placed.normal = glm::vec3(glm::normalize(stand.forward * direction.x + stand.left * direction.y + stand.up * direction.z));
|
|
}
|
|
vertices.push_back(placed);
|
|
}
|
|
if (definition.mirror)
|
|
{
|
|
// a mirror image turns the triangles inside out
|
|
for (auto i = first; i + 2 < vertices.size(); i += 3)
|
|
std::swap(vertices[i + 1], vertices[i + 2]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (auto &entry : batches)
|
|
{
|
|
if (entry.vertices.empty())
|
|
continue;
|
|
scene::shape_node shape;
|
|
shape.make_terrain(entry.material, std::move(entry.vertices), glm::dvec3{0.0});
|
|
shape.lighting(entry.lighting);
|
|
shape.translucent(entry.translucent);
|
|
shapes.emplace_back(std::move(shape));
|
|
}
|
|
return shapes;
|
|
}
|
|
|
|
void sweep_node::show()
|
|
{
|
|
if (m_merged || m_shapes.shown() || m_editorremoved)
|
|
{
|
|
return;
|
|
}
|
|
m_shapes.show(create_shapes(), location());
|
|
}
|
|
|
|
void sweep_node::hide()
|
|
{
|
|
m_shapes.hide();
|
|
}
|
|
|
|
float sweep_node::radius_()
|
|
{
|
|
return static_cast<float>(0.5 * length() + std::abs(m_state.lateral) + 25.0);
|
|
}
|
|
|
|
void sweep_node::serialize_(std::ostream &Output) const
|
|
{
|
|
// TODO: implement
|
|
}
|
|
|
|
void sweep_node::deserialize_(std::istream &Input)
|
|
{
|
|
// TODO: implement
|
|
}
|
|
|
|
void sweep_node::export_as_text_(std::ostream &Output) const
|
|
{
|
|
auto const &sweep{m_state};
|
|
Output << "sweep " << sweep.model << ' ' << (sweep.skin.empty() ? std::string{"none"} : sweep.skin) << ' ';
|
|
Output << "mode " << (sweep.bend ? "bend" : "repeat") << ' ';
|
|
if (sweep.step > 0.0)
|
|
{
|
|
Output << "step " << sweep.step << ' ';
|
|
}
|
|
if (sweep.from != 0.0 || sweep.to >= 0.0)
|
|
{
|
|
Output << "range " << sweep.from << ' ' << sweep.to << ' ';
|
|
}
|
|
if (sweep.lateral != 0.0 || sweep.height != 0.0)
|
|
{
|
|
Output << "offset " << sweep.lateral << ' ' << sweep.height << ' ';
|
|
}
|
|
if (sweep.along_x)
|
|
{
|
|
Output << "axis x ";
|
|
}
|
|
if (sweep.flip)
|
|
{
|
|
Output << "flip ";
|
|
}
|
|
if (sweep.mirror)
|
|
{
|
|
Output << "mirror ";
|
|
}
|
|
if (sweep.tilt)
|
|
{
|
|
Output << "tilt ";
|
|
}
|
|
if (false == sweep.face)
|
|
{
|
|
Output << "noface ";
|
|
}
|
|
if (sweep.widen)
|
|
{
|
|
Output << "widen ";
|
|
}
|
|
if (sweep.platform)
|
|
{
|
|
Output << "platform ";
|
|
}
|
|
if (false == sweep.parameters.empty())
|
|
{
|
|
Output << "parameters " << sweep.parameters.size() << ' ';
|
|
for (auto const ¶meter : sweep.parameters)
|
|
Output << (parameter.empty() ? std::string{"none"} : parameter) << ' ';
|
|
}
|
|
if (sweep.side_anchor != 0 || sweep.height_anchor != 0)
|
|
{
|
|
char const *sides[] = {"origin", "near", "far", "centre", "point"};
|
|
char const *heights[] = {"origin", "bottom", "top", "edge", "point"};
|
|
Output << "anchor " << sides[std::clamp(sweep.side_anchor, 0, 4)] << ' ' << heights[std::clamp(sweep.height_anchor, 0, 4)] << ' ';
|
|
}
|
|
if (sweep.side_anchor == 4 || sweep.height_anchor == 4)
|
|
{
|
|
Output << "point " << sweep.point.x << ' ' << sweep.point.y << ' ' << sweep.point.z << ' ';
|
|
}
|
|
if (sweep.scale != glm::dvec3{1.0})
|
|
{
|
|
Output << "scale " << sweep.scale.x << ' ' << sweep.scale.y << ' ' << sweep.scale.z << ' ';
|
|
}
|
|
auto const precision{Output.precision(std::numeric_limits<double>::digits10)};
|
|
for (auto const &piece : sweep.pieces)
|
|
{
|
|
auto const &start{piece.points[segment_data::point::start]};
|
|
auto const &control1{piece.points[segment_data::point::control1]};
|
|
auto const &control2{piece.points[segment_data::point::control2]};
|
|
auto const &end{piece.points[segment_data::point::end]};
|
|
Output << "\npiece " << start.x << ' ' << start.y << ' ' << start.z << ' ' << piece.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' '
|
|
<< end.x << ' ' << end.y << ' ' << end.z << ' ' << piece.rolls[1] << ' ' << piece.radius;
|
|
}
|
|
Output.precision(precision);
|
|
Output << "\nendsweep\n";
|
|
}
|
|
|
|
void sweep_table::create_geometry(scene::scratch_data &Scratchpad)
|
|
{
|
|
if (simulation::Region == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
for (auto *sweep : m_items)
|
|
{
|
|
if (sweep == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
if (sweep->bridge())
|
|
{
|
|
if (Global.editor_session)
|
|
sweep->mark_dirty();
|
|
}
|
|
else if (sweep->definition().widen || sweep->definition().platform)
|
|
{
|
|
sweep->refresh();
|
|
}
|
|
if (Global.editor_session && false == Global.NvRenderer)
|
|
{
|
|
// scenery opened for editing: the copies are kept apart, so they can be changed later
|
|
sweep->show();
|
|
continue;
|
|
}
|
|
for (auto &shape : sweep->create_shapes())
|
|
{
|
|
simulation::Region->insert(shape, Scratchpad, false);
|
|
}
|
|
sweep->merged(true);
|
|
}
|
|
}
|