17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 20:19:20 +02:00
Files
maszyna/world/Sweep.cpp
maj00r d4791f6a29 feat(editor): a model or an include standing by a track bent along it (B, or the Bend along the track section of the toolset): it stays where it stood and follows the track, through the joined tracks as far as it reaches; reference point at its start, origin, middle, end or a point picked on the model, across at its origin, the side facing the track, the far side or its middle, up at its origin, bottom, top or the top of its edge by the track; the blue cross of a bent model dragged for its offset, with Shift for its height and with Ctrl along the track, the values shown at the cursor; a bent model clicked to edit; one step of the undo history; scale of the model kept, parts lit only at night left out; edges of platforms told by the name of the model and by the overhang of their edge over the foot: put 1.725 m from the axis at the height the name gives, turned with the edge to the track when they stood the other way; platforms stand on the plane of the rail heads carried on to their edge (PKP PLK ST-T2), the gauge widening led in from the tracks beyond the ends and from the other path of a switch; gaps between the pieces of a sweep filled with the paths which join them; Platforms and models tab left out of the track editor; laid track eased into the curvature of the one it joins with a transition curve; File > Open scenery... starts the editor again with the scenery picked from a list; the camera saved with the scenery; T and Shift+T turn each turntable to its next and previous position by event launchers written on each save; a turntable track deleted with its template; a scenery with no trainset gets a WMB10 on its first track; vehicle placed where the track is clicked; gauge check takes in the bent models; Polish translation of the editor
2026-10-08 00:51:40 +02:00

1369 lines
47 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 (next.has_value() || 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;
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 &current{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<std::pair<double, double>> const &Spans)
{
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 matches = [](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 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;
};
for (std::size_t piece = 0; piece < m_state.pieces.size() && piece < Spans.size(); ++piece)
{
auto const &own{m_state.pieces[piece]};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->eType != tt_Switch || track->m_paths.size() < 2 ||
plan_distance(track->m_paths.front().points[segment_data::point::start], own.points[segment_data::point::start]) > 300.0)
continue;
for (std::size_t path = 0; path < track->m_paths.size(); ++path)
{
auto const forward{matches(own, track->m_paths[path])};
if (false == forward && false == matches(own, turned(track->m_paths[path])))
continue;
for (std::size_t other = 0; other < track->m_paths.size(); ++other)
{
if (other == path)
continue;
std::vector<sample> chain;
double station{0.0};
if (forward)
{
for (auto const &sample : m_samples)
if (sample.station >= Spans[piece].first - outer_reach && sample.station < Spans[piece].first)
{
station = sample.station;
chain.push_back(sample);
}
sample_piece(track->m_paths[other], chain, station);
}
else
{
sample_piece(turned(track->m_paths[other]), chain, station);
auto const shift{station - Spans[piece].second};
for (auto const &sample : m_samples)
if (sample.station > Spans[piece].second && sample.station <= Spans[piece].second + outer_reach)
{
chain.push_back(sample);
chain.back().station += shift;
}
}
gauge_along(chain);
for (auto &here : m_samples)
{
if (here.station < Spans[piece].first - outer_reach || here.station > Spans[piece].second + outer_reach)
continue;
glm::dvec3 const left{-here.tangent.z, 0.0, here.tangent.x};
sample const *nearest{nullptr};
auto best{std::numeric_limits<double>::max()};
for (auto const &point : chain)
{
auto const along{std::abs(glm::dot(glm::dvec3{point.position.x - here.position.x, 0.0, point.position.z - here.position.z}, here.tangent))};
if (along < best)
{
best = along;
nearest = &point;
}
}
if (nearest == nullptr || best > 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::length(left) > 1e-9 ? glm::normalize(left) : left)};
if (offset > 0.05)
continue;
auto const needed{offset + total(*nearest)};
if (needed > total(here))
{
here.widening[side] = needed;
here.cant[side] = 0.0;
}
}
}
}
}
}
}
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<std::pair<double, double>> spans;
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)
{
auto const begin{station};
sample_piece(piece, m_samples, station);
spans.emplace_back(begin - head, station - head);
}
auto const last{m_samples.size()};
for (auto const &piece : after)
sample_piece(piece, m_samples, station);
gauge_along(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;
if ((m_state.widen || m_state.platform) && m_samples.size() > 2)
{
beside_switches(spans);
}
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 &parameter : 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);
}
}