17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 11:37:29 +01:00
Files
maszyna/world/catenary_tensioner.cpp
Claude f30fefd91c Sieć trakcyjna: przęsła wg typów sieci, wysięgniki z katalogu, bramki, kotwienia i edytor sieci
Sam kod exe z gałęzi siec-trakcyjna (bez testów, notatek i danych z tests/catenary).
Dane (data/traction_*.txt) leżą poza repozytorium.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Tqn7T8D1Gx3ptKtZgNtX5
2026-10-23 23:15:00 +02:00

620 lines
31 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 "world/catenary_tensioner.h"
#include <algorithm>
#include <cmath>
#include <fstream>
#include <sstream>
namespace catenary {
namespace {
double const kPi { 3.14159265358979323846 };
std::vector<std::string> words_of( std::string Line ) {
if( auto const comment { Line.find( "//" ) }; comment != std::string::npos ) { Line.erase( comment ); }
std::istringstream stream { Line };
std::vector<std::string> words;
std::string word;
while( stream >> word ) { words.push_back( word ); }
return words;
}
bool number_of( std::string const &Word, double &Value ) {
try {
std::size_t used { 0 };
Value = std::stod( Word, &used );
return used == Word.size();
}
catch( ... ) { return false; }
}
// położenie z interpolacją liniową w tablicy rosnącej; Index i Fraction między Index a Index + 1. false poza zakresem
bool bracket( std::vector<double> const &Values, double const Value, std::size_t &Index, double &Fraction ) {
if( Values.size() < 2 ) { Index = 0; Fraction = 0.0; return Values.size() == 1 && Value == Values.front(); }
auto inside { true };
auto value { Value };
if( value < Values.front() ) { value = Values.front(); inside = false; }
if( value > Values.back() ) { value = Values.back(); inside = false; }
Index = 0;
while( Index + 2 < Values.size() && value > Values[ Index + 1 ] ) { ++Index; }
auto const span { Values[ Index + 1 ] - Values[ Index ] };
Fraction = span > 0.0 ? ( value - Values[ Index ] ) / span : 0.0;
return inside;
}
struct vec { double x { 0.0 }, y { 0.0 }, z { 0.0 }; };
vec operator+( vec const &A, vec const &B ) { return { A.x + B.x, A.y + B.y, A.z + B.z }; }
vec operator-( vec const &A, vec const &B ) { return { A.x - B.x, A.y - B.y, A.z - B.z }; }
vec operator*( vec const &A, double const B ) { return { A.x * B, A.y * B, A.z * B }; }
double dot( vec const &A, vec const &B ) { return A.x * B.x + A.y * B.y + A.z * B.z; }
vec cross( vec const &A, vec const &B ) { return { A.y * B.z - A.z * B.y, A.z * B.x - A.x * B.z, A.x * B.y - A.y * B.x }; }
double length( vec const &A ) { return std::sqrt( dot( A, A ) ); }
vec unit( vec const &A ) { auto const l { length( A ) }; return l > 1e-12 ? A * ( 1.0 / l ) : vec { 1.0, 0.0, 0.0 }; }
// obrót: kolumny to osie części w układzie urządzenia
void set_rotation( tensioner_placement &Placement, vec const &X, vec const &Y, vec const &Z ) {
vec const axes[ 3 ] { X, Y, Z };
for( int row = 0; row < 3; ++row ) {
for( int column = 0; column < 3; ++column ) {
auto const &axis { axes[ column ] };
Placement.rotation[ row * 3 + column ] = ( row == 0 ? axis.x : row == 1 ? axis.y : axis.z );
}
}
}
// oś Y części możliwie wzdłuż Y urządzenia, prostopadła do Direction
vec side_of( vec const &Direction ) {
vec const y { 0.0, 1.0, 0.0 };
auto const side { y - Direction * dot( y, Direction ) };
return length( side ) > 1e-9 ? unit( side ) : vec { 0.0, 0.0, 1.0 };
}
tensioner_placement placed( std::string const &Role, std::string const &Part, vec const &At ) {
tensioner_placement placement;
placement.role = Role;
placement.part = Part;
placement.position[ 0 ] = At.x;
placement.position[ 1 ] = At.y;
placement.position[ 2 ] = At.z;
return placement;
}
// część liniowa: +X części w kierunku Direction
tensioner_placement placed_along( std::string const &Role, std::string const &Part, vec const &At, vec const &Direction ) {
auto placement { placed( Role, Part, At ) };
auto const x { unit( Direction ) };
auto const y { side_of( x ) };
set_rotation( placement, x, y, cross( x, y ) );
return placement;
}
// łańcuch części liniowych od From w kierunku Direction; zwraca koniec
vec place_chain( std::vector<tensioner_link> const &Links, std::string const &Role, vec From, vec const &Direction, tensioner_result &Result ) {
auto const step { unit( Direction ) };
int index { 0 };
for( auto const &link : Links ) {
Result.parts.push_back( placed_along( Role + "." + std::to_string( ++index ), link.name, From, step ) );
From = From + step * link.length;
}
return From;
}
} // namespace
void tensioner_table::formula_values( double const Temperature, double const Distance, double &B, double &S ) const {
auto const shift { Distance * alpha * ( Temperature - tn ) };
B = bn - shift;
S = sn - 4.0 * shift;
}
int tensioner_color( std::string const &Name ) {
if( Name == "zolty" || Name == "1" ) { return 1; }
if( Name == "szary" || Name == "2" ) { return 2; }
if( Name == "zolty_pedzel" || Name == "3" ) { return 3; }
if( Name == "szary_brzydki" || Name == "4" ) { return 4; }
return 0;
}
bool tensioner_table::lookup( double const Temperature, double const Distance, double &B, double &S ) const {
if( formula ) {
formula_values( Temperature, Distance, B, S );
return true;
}
B = 0.0;
S = 0.0;
if( temperatures.empty() || distances.empty() ) { return false; }
std::size_t row { 0 }, column { 0 };
double down { 0.0 }, across { 0.0 };
auto const inside_d { bracket( distances, Distance, row, down ) };
auto const inside_t { bracket( temperatures, Temperature, column, across ) };
auto const value = [ & ]( std::vector<std::vector<double>> const &Table ) {
auto const at = [ & ]( std::size_t const R, std::size_t const C ) {
return Table[ std::min( R, Table.size() - 1 ) ][ std::min( C, Table[ 0 ].size() - 1 ) ];
};
auto const top { at( row, column ) * ( 1.0 - across ) + at( row, column + 1 ) * across };
auto const bottom { at( row + 1, column ) * ( 1.0 - across ) + at( row + 1, column + 1 ) * across };
return top * ( 1.0 - down ) + bottom * down;
};
B = value( b );
S = value( s );
return inside_d && inside_t;
}
tensioner_type const *tensioner_device::type( int const Code ) const {
for( auto const &entry : types ) { if( entry.code == Code ) { return &entry; } }
return nullptr;
}
bool tensioner_catalog::load( std::string const &Filename, std::vector<std::string> &Errors ) {
std::ifstream file { Filename };
if( false == file.is_open() ) { return false; }
std::vector<std::string> lines;
std::string line;
while( std::getline( file, line ) ) {
if( false == line.empty() && line.back() == '\r' ) { line.pop_back(); }
lines.push_back( line );
}
parse( lines, Filename, Errors );
return true;
}
void tensioner_catalog::parse( std::vector<std::string> const &Lines, std::string const &Source, std::vector<std::string> &Errors ) {
tensioner_table *table { nullptr };
tensioner_device *device { nullptr };
int number { 0 };
auto const report = [ & ]( std::string const &Text ) { Errors.push_back( Source + ":" + std::to_string( number ) + ": " + Text ); };
// pary "część długość" od pozycji From
auto const links_of = [ & ]( std::vector<std::string> const &Words, std::size_t const From, std::vector<tensioner_link> &Links ) {
for( auto k { From }; k + 1 < Words.size(); k += 2 ) {
tensioner_link link;
link.name = Words[ k ];
if( false == number_of( Words[ k + 1 ], link.length ) ) { report( "bad length \"" + Words[ k + 1 ] + "\"" ); return; }
Links.push_back( link );
}
if( ( Words.size() - From ) % 2 != 0 ) { report( "\"" + Words[ 0 ] + "\": a part without a length" ); }
};
for( auto const &text : Lines ) {
++number;
auto const words { words_of( text ) };
if( words.empty() ) { continue; }
auto const &keyword { words[ 0 ] };
// tablica montażowa
if( table != nullptr ) {
if( keyword == "endtable" ) {
for( auto const &row : table->b ) {
if( row.size() != table->temperatures.size() ) { report( "table " + table->name + ": a row of b has a wrong number of values" ); }
}
for( auto const &row : table->s ) {
if( row.size() != table->temperatures.size() ) { report( "table " + table->name + ": a row of s has a wrong number of values" ); }
}
table = nullptr;
continue;
}
if( keyword == "temperatures" ) {
table->temperatures.clear();
for( std::size_t k = 1; k < words.size(); ++k ) {
double value;
if( number_of( words[ k ], value ) ) { table->temperatures.push_back( value ); }
else { report( "bad temperature \"" + words[ k ] + "\"" ); }
}
continue;
}
if( keyword == "row" && words.size() > 2 ) {
double distance;
if( false == number_of( words[ 1 ], distance ) ) { report( "bad distance \"" + words[ 1 ] + "\"" ); continue; }
std::vector<double> *target { nullptr };
table->distances.push_back( distance );
table->b.emplace_back();
table->s.emplace_back();
for( std::size_t k = 2; k < words.size(); ++k ) {
if( words[ k ] == "b" ) { target = &table->b.back(); continue; }
if( words[ k ] == "s" ) { target = &table->s.back(); continue; }
double value;
if( target == nullptr || false == number_of( words[ k ], value ) ) { report( "bad value \"" + words[ k ] + "\"" ); continue; }
target->push_back( value );
}
continue;
}
report( "unknown keyword \"" + keyword + "\" in a table" );
continue;
}
// urządzenie
if( device != nullptr ) {
if( keyword == "enddevice" ) { device = nullptr; continue; }
if( keyword == "mount" ) { links_of( words, 1, device->mount ); continue; }
if( keyword == "holder" ) { links_of( words, 1, device->holder ); continue; }
if( keyword == "hanger" ) { links_of( words, 1, device->hanger ); continue; }
if( keyword == "messenger" ) { links_of( words, 1, device->messenger ); continue; }
if( keyword == "contact" ) { links_of( words, 1, device->contact ); continue; }
if( ( keyword == "messenger_clamp" || keyword == "contact_clamp" || keyword == "equalizer" ) && words.size() >= 3 ) {
auto &link { keyword == "messenger_clamp" ? device->messenger_clamp : keyword == "contact_clamp" ? device->contact_clamp : device->equalizer };
link.name = words[ 1 ];
if( false == number_of( words[ 2 ], link.length ) ) { report( "bad length \"" + words[ 2 ] + "\"" ); }
if( keyword == "equalizer" && words.size() >= 5 && words[ 3 ] == "spread" ) { number_of( words[ 4 ], device->equalizer_spread ); }
continue;
}
if( keyword == "type" && words.size() >= 2 ) {
tensioner_type type;
double code;
if( false == number_of( words[ 1 ], code ) ) { report( "bad type \"" + words[ 1 ] + "\"" ); continue; }
type.code = static_cast<int>( code );
for( std::size_t k = 2; k < words.size(); ) {
auto const &key { words[ k++ ] };
if( key == "stack" ) {
while( k + 1 < words.size() && m_weights.end() != std::find_if( m_weights.begin(), m_weights.end(), [ & ]( tensioner_weight const &W ) { return W.name == words[ k ]; } ) ) {
double count;
if( false == number_of( words[ k + 1 ], count ) ) { report( "bad count \"" + words[ k + 1 ] + "\"" ); break; }
type.stack.emplace_back( words[ k ], static_cast<int>( count ) );
k += 2;
}
}
else if( key == "lever" && k + 2 < words.size() ) {
type.messenger_hole = words[ k++ ];
type.contact_hole = words[ k++ ];
type.axis_hole = words[ k++ ];
}
else if( key == "rod" && k < words.size() ) { type.rod = words[ k++ ]; }
else if( key == "clamp" && k < words.size() ) { type.messenger_clamp = words[ k++ ]; }
else { report( "type " + words[ 1 ] + ": unknown keyword \"" + key + "\" (a weight has to be defined before the device)" ); break; }
}
device->types.push_back( type );
continue;
}
report( "unknown keyword \"" + keyword + "\" in a device" );
continue;
}
if( keyword == "table" && words.size() >= 2 ) {
// "table <nazwa> base <inna> shift_b <m> shift_s <m> ..." - jednowierszowa, wartości innej tablicy przesunięte
// o stałą (tablice 0011-6/-7/-8 różnią się od 0011-1 tylko położeniem nominalnym)
tensioner_table entry;
entry.name = words[ 1 ];
double shiftb { 0.0 }, shifts { 0.0 };
auto derived { false };
for( std::size_t k = 2; k + 1 < words.size(); k += 2 ) {
if( words[ k ] == "height" ) { number_of( words[ k + 1 ], entry.height ); }
else if( words[ k ] == "rod" ) { number_of( words[ k + 1 ], entry.rod ); }
else if( words[ k ] == "shift_b" ) { number_of( words[ k + 1 ], shiftb ); }
else if( words[ k ] == "shift_s" ) { number_of( words[ k + 1 ], shifts ); }
else if( words[ k ] == "formula" ) {
// "formula <bn> <sn> <tn> <alfa>": cztery liczby
if( k + 4 < words.size() && number_of( words[ k + 1 ], entry.bn ) && number_of( words[ k + 2 ], entry.sn )
&& number_of( words[ k + 3 ], entry.tn ) && number_of( words[ k + 4 ], entry.alpha ) ) { entry.formula = true; }
else { report( "table " + entry.name + ": formula needs bn sn tn alpha" ); }
k += 3; // pętla doda jeszcze 2
}
else if( words[ k ] == "base" ) {
derived = true;
auto const *base { this->table( words[ k + 1 ] ) };
if( base == nullptr ) { report( "table " + entry.name + ": no base table " + words[ k + 1 ] + " before it" ); continue; }
entry.temperatures = base->temperatures;
entry.distances = base->distances;
entry.b = base->b;
entry.s = base->s;
entry.formula = base->formula;
entry.bn = base->bn;
entry.sn = base->sn;
entry.tn = base->tn;
entry.alpha = base->alpha;
}
else { report( "table " + entry.name + ": unknown keyword \"" + words[ k ] + "\"" ); }
}
if( derived ) {
for( auto &row : entry.b ) { for( auto &value : row ) { value += shiftb; } }
for( auto &row : entry.s ) { for( auto &value : row ) { value += shifts; } }
entry.bn += shiftb;
entry.sn += shifts;
}
m_tables.push_back( entry );
if( false == derived ) { table = &m_tables.back(); }
continue;
}
if( keyword == "weight" && words.size() >= 2 ) {
tensioner_weight weight;
weight.name = words[ 1 ];
for( std::size_t k = 2; k + 1 < words.size(); k += 2 ) {
if( words[ k ] == "height" ) { number_of( words[ k + 1 ], weight.height ); }
else if( words[ k ] == "mass" ) { number_of( words[ k + 1 ], weight.mass ); }
else if( words[ k ] == "turn" ) { number_of( words[ k + 1 ], weight.turn ); }
else if( words[ k ] == "model" ) { weight.model = words[ k + 1 ]; }
else if( words[ k ] == "offset" ) { double value { 0.0 }; number_of( words[ k + 1 ], value ); weight.offset = static_cast<int>( value ); }
else { report( "weight " + weight.name + ": unknown keyword \"" + words[ k ] + "\"" ); }
}
m_weights.push_back( weight );
continue;
}
if( keyword == "pulley" && words.size() >= 2 ) {
tensioner_pulley pulley;
pulley.name = words[ 1 ];
for( std::size_t k = 2; k + 1 < words.size(); k += 2 ) {
double value { 0.0 };
number_of( words[ k + 1 ], value );
if( words[ k ] == "diameter" ) { pulley.diameter = value; }
else if( words[ k ] == "width" ) { pulley.width = value; }
else if( words[ k ] == "grooves" ) { pulley.grooves = static_cast<int>( value ); }
else { report( "pulley " + pulley.name + ": unknown keyword \"" + words[ k ] + "\"" ); }
}
m_pulleys.push_back( pulley );
continue;
}
if( keyword == "lever" && words.size() >= 2 ) {
tensioner_lever lever;
lever.name = words[ 1 ];
for( std::size_t k = 2; k < words.size(); ) {
if( words[ k ] == "hole" && k + 3 < words.size() ) {
double along, aside;
if( number_of( words[ k + 2 ], along ) && number_of( words[ k + 3 ], aside ) ) { lever.holes[ words[ k + 1 ] ] = { along, aside }; }
else { report( "lever " + lever.name + ": bad hole \"" + words[ k + 1 ] + "\"" ); }
k += 4;
}
else { report( "lever " + lever.name + ": unknown keyword \"" + words[ k ] + "\"" ); break; }
}
m_levers.push_back( lever );
continue;
}
if( keyword == "part" && words.size() >= 4 && words[ 2 ] == "length" ) {
double value;
if( number_of( words[ 3 ], value ) ) { m_lengths[ words[ 1 ] ] = value; }
else { report( "part " + words[ 1 ] + ": bad length" ); }
continue;
}
if( keyword == "device" && words.size() >= 2 ) {
m_devices.emplace_back();
device = &m_devices.back();
device->name = words[ 1 ];
for( std::size_t k = 2; k + 1 < words.size(); k += 2 ) {
auto const &key { words[ k ] };
auto const &value { words[ k + 1 ] };
if( key == "table" ) { device->table = value; }
else if( key == "pulley" ) { device->pulley = value; }
else if( key == "lever" ) { device->lever = value; }
else if( key == "rod" ) { device->rod = value; }
else if( key == "rope" ) { device->rope = value; }
else if( key == "rope_diameter" ) { number_of( value, device->rope_diameter ); }
else { report( "device " + device->name + ": unknown keyword \"" + key + "\"" ); }
}
continue;
}
report( "unknown keyword \"" + keyword + "\"" );
}
if( table != nullptr ) { report( "table " + table->name + " without endtable" ); }
if( device != nullptr ) { report( "device " + device->name + " without enddevice" ); }
}
tensioner_table const *tensioner_catalog::table( std::string const &Name ) const {
for( auto const &entry : m_tables ) { if( entry.name == Name ) { return &entry; } }
return nullptr;
}
tensioner_weight const *tensioner_catalog::weight( std::string const &Name ) const {
for( auto const &entry : m_weights ) { if( entry.name == Name ) { return &entry; } }
return nullptr;
}
tensioner_pulley const *tensioner_catalog::pulley( std::string const &Name ) const {
for( auto const &entry : m_pulleys ) { if( entry.name == Name ) { return &entry; } }
return nullptr;
}
tensioner_lever const *tensioner_catalog::lever( std::string const &Name ) const {
for( auto const &entry : m_levers ) { if( entry.name == Name ) { return &entry; } }
return nullptr;
}
tensioner_device const *tensioner_catalog::device( std::string const &Name ) const {
for( auto const &entry : m_devices ) { if( entry.name == Name ) { return &entry; } }
return nullptr;
}
bool solve_tensioner( tensioner_catalog const &Catalog, tensioner_device const &Device, tensioner_input const &Input, tensioner_result &Result ) {
Result = tensioner_result();
auto const fail = [ & ]( std::string const &Text ) { Result.warnings.push_back( Text ); return false; };
auto const *type { Device.type( Input.type ) };
if( type == nullptr ) { return fail( "device " + Device.name + " has no data for network type " + std::to_string( Input.type ) ); }
auto const *table { Catalog.table( Device.table ) };
if( table == nullptr ) { return fail( "no table " + Device.table ); }
auto const *pulley { Catalog.pulley( Device.pulley ) };
if( pulley == nullptr ) { return fail( "no pulley " + Device.pulley ); }
auto const *lever { Catalog.lever( Device.lever ) };
if( lever == nullptr ) { return fail( "no lever " + Device.lever ); }
auto const hole = [ & ]( std::string const &Name ) -> std::pair<double, double> const * {
auto const found { lever->holes.find( Name ) };
return found == lever->holes.end() ? nullptr : &found->second;
};
auto const *messengerhole { hole( type->messenger_hole ) };
auto const *contacthole { hole( type->contact_hole ) };
auto const *axishole { hole( type->axis_hole ) };
if( messengerhole == nullptr || contacthole == nullptr || axishole == nullptr ) {
return fail( "lever " + lever->name + " has no hole " + type->messenger_hole + "/" + type->contact_hole + "/" + type->axis_hole );
}
auto const rodname { type->rod.empty() ? Device.rod : type->rod };
auto const rodlength { [ & ] { auto const found { Catalog.part_lengths().find( rodname ) }; return found != Catalog.part_lengths().end() ? found->second : table->rod; }() };
// położenie z tablicy, trwałe wydłużenie sieci przesuwa rolkę do konstrukcji (ciężary 4 razy więcej)
double b, s;
if( false == table->lookup( Input.temperature, Input.distance, b, s ) ) {
Result.warnings.push_back( "temperature or distance outside table " + table->name + ", taken from its edge" );
}
b -= Input.elongation;
s -= 4.0 * Input.elongation;
// pręt innej długości niż w tablicy: spód ciężarów niżej o różnicę (uwaga 3 na karcie 0011-1), góra pręta na tej samej linie
s -= rodlength - table->rod;
// stos ciężarów od dołu, brakujące z góry
std::vector<tensioner_weight const *> stack;
for( auto const &[ name, count ] : type->stack ) {
auto const *weight { Catalog.weight( name ) };
if( weight == nullptr ) { return fail( "no weight " + name ); }
for( int k = 0; k < count; ++k ) { stack.push_back( weight ); }
}
for( int k = 0; k < Input.missing && false == stack.empty(); ++k ) { stack.pop_back(); }
double mass { 0.0 }, stackheight { 0.0 };
for( auto const *weight : stack ) { mass += weight->mass; stackheight += weight->height; }
Result.weights = static_cast<int>( stack.size() );
Result.stack_height = stackheight;
Result.tension = 4.0 * mass * Input.gravity;
// ciężary nie zejdą niżej niż grunt: lina nie puści rolki bliżej konstrukcji, sieć traci naciąg
if( s < Input.ground ) {
b += ( Input.ground - s ) / 4.0;
s = Input.ground;
Result.grounded = true;
Result.tension = 0.0;
Result.warnings.push_back( "the weights rest on the ground: the network has no tension from the device" );
}
Result.b = b;
Result.s = s;
auto const height { table->height };
vec const out { 1.0, 0.0, 0.0 }, down { 0.0, 0.0, -1.0 };
// rolka stała na uchwytach od czoła konstrukcji, rolka ruchoma w b
auto const fixedaxle { place_chain( Device.mount, "mount", vec { 0.0, 0.0, height }, out, Result ) };
vec const movingaxle { b, 0.0, height };
auto const radius { 0.5 * pulley->diameter + 0.5 * Device.rope_diameter };
if( movingaxle.x - fixedaxle.x < 2.0 * radius ) { Result.warnings.push_back( "the pulleys overlap: the network pulled the moving one to the structure" ); }
{
auto fixed { placed( "pulley_fixed", pulley->name, fixedaxle ) };
Result.parts.push_back( fixed );
Result.parts.push_back( placed( "pulley_moving", pulley->name, movingaxle ) );
}
auto const axispoint { place_chain( Device.holder, "holder", movingaxle, out, Result ) };
// dźwignia: otwór osi na axispoint; przy otworze liny niżej niż otwór przewodów odwrócona
auto const flipped { messengerhole->first < contacthole->first };
auto const sign { flipped ? -1.0 : 1.0 };
auto const holepoint = [ & ]( std::pair<double, double> const &Hole ) {
return axispoint + vec { sign * ( Hole.second - axishole->second ), 0.0, sign * ( Hole.first - axishole->first ) };
};
{
auto placement { placed( "lever", lever->name, holepoint( { 0.0, 0.0 } ) ) };
set_rotation( placement, vec { sign, 0.0, 0.0 }, vec { 0.0, 1.0, 0.0 }, vec { 0.0, 0.0, sign } );
Result.parts.push_back( placement );
}
// podział naciągu dźwignią: momenty względem osi
auto const armmessenger { std::abs( messengerhole->first - axishole->first ) };
auto const armcontact { std::abs( contacthole->first - axishole->first ) };
if( armmessenger + armcontact > 1e-9 ) {
Result.messenger_tension = Result.tension * armcontact / ( armmessenger + armcontact );
Result.contact_tension = Result.tension - Result.messenger_tension;
}
// łańcuchy do drutów
{
auto end { place_chain( Device.messenger, "messenger", holepoint( *messengerhole ), out, Result ) };
auto const clamp { type->messenger_clamp.empty() ? Device.messenger_clamp.name : type->messenger_clamp };
if( false == clamp.empty() ) {
Result.parts.push_back( placed_along( "messenger_clamp", clamp, end, out ) );
end = end + out * Device.messenger_clamp.length;
}
Result.messenger_end[ 0 ] = end.x;
Result.messenger_end[ 1 ] = end.y;
Result.messenger_end[ 2 ] = end.z;
}
{
auto end { place_chain( Device.contact, "contact", holepoint( *contacthole ), out, Result ) };
std::vector<vec> ends;
if( Input.contacts >= 2 && false == Device.equalizer.name.empty() ) {
Result.parts.push_back( placed_along( "equalizer", Device.equalizer.name, end, out ) );
end = end + out * Device.equalizer.length;
for( int k = 0; k < Input.contacts; ++k ) {
auto const offset { ( k - 0.5 * ( Input.contacts - 1 ) ) * Device.equalizer_spread };
ends.push_back( end + vec { 0.0, offset, 0.0 } );
}
}
else {
ends.push_back( end );
}
for( std::size_t k = 0; k < ends.size(); ++k ) {
auto at { ends[ k ] };
if( false == Device.contact_clamp.name.empty() ) {
Result.parts.push_back( placed_along( "contact_clamp." + std::to_string( k + 1 ), Device.contact_clamp.name, at, out ) );
at = at + out * Device.contact_clamp.length;
}
Result.contact_ends.push_back( { at.x, at.y, at.z } );
}
}
// lina: koniec przy rolce stałej (dołem), żyły między rowkami A i B, wolny koniec z rolki stałej w dół
auto const groove { pulley->grooves >= 2 ? 0.25 * pulley->width : 0.0 };
double const ya { -groove }, yb { groove };
auto &rope { Result.rope };
auto const point = [ & ]( vec const &P ) { rope.push_back( { P.x, P.y, P.z } ); };
auto const arc = [ & ]( vec const &Center, double const Y, double const From, double const To ) {
// kąt w płaszczyźnie X-Z od +X w stronę +Z; punkty co najwyżej co 15 stopni, bez pierwszego
auto const steps { std::max( 1, static_cast<int>( std::ceil( std::abs( To - From ) / ( kPi / 12.0 ) ) ) ) };
for( int k = 1; k <= steps; ++k ) {
auto const angle { From + ( To - From ) * k / steps };
point( vec { Center.x + radius * std::cos( angle ), Y, Center.z + radius * std::sin( angle ) } );
}
};
point( vec { fixedaxle.x, 0.0, height - radius } );
point( vec { movingaxle.x, ya, height - radius } );
arc( movingaxle, ya, -0.5 * kPi, 0.5 * kPi );
point( vec { fixedaxle.x, ya, height + radius } );
arc( fixedaxle, ya, 0.5 * kPi, 1.5 * kPi );
point( vec { movingaxle.x, yb, height - radius } );
arc( movingaxle, yb, -0.5 * kPi, 0.5 * kPi );
point( vec { fixedaxle.x, yb, height + radius } );
arc( fixedaxle, yb, 0.5 * kPi, kPi );
// pręt z ciężarami pod liną, łączniki między liną a prętem
double hangerlength { 0.0 };
for( auto const &link : Device.hanger ) { hangerlength += link.length; }
auto const columnx { fixedaxle.x - radius };
vec const ropeend { columnx, yb, s + rodlength + hangerlength };
if( ropeend.z > height - 0.05 ) { Result.warnings.push_back( "the weights would hang above the pulley: rod or hangers too long for the table" ); }
point( ropeend );
place_chain( Device.hanger, "hanger", ropeend, down, Result );
{
auto placement { placed( "rod", rodname, vec { columnx, yb, s } ) };
Result.parts.push_back( placement );
}
auto z { s };
int index { 0 };
int turns { 0 }; // ciężary już leżące na pręcie (każdy kolejny obrócony o "turn" swojego rodzaju)
for( auto const *weight : stack ) {
++index;
auto const top { index == static_cast<int>( stack.size() ) };
auto const &color { top && false == Input.top_color.empty() ? Input.top_color : Input.color };
auto model { weight->name };
if( false == weight->model.empty() ) {
auto number { tensioner_color( color ) };
if( number == 0 ) { number = 2; }
number += weight->offset;
model = weight->model + "-" + ( number < 10 ? "0" : "" ) + std::to_string( number );
}
auto placement { placed( "weight." + std::to_string( index ), model, vec { columnx, yb, z } ) };
auto const angle { weight->turn * ( turns++ ) * kPi / 180.0 };
set_rotation( placement, vec { std::cos( angle ), std::sin( angle ), 0.0 }, vec { -std::sin( angle ), std::cos( angle ), 0.0 }, vec { 0.0, 0.0, 1.0 } );
Result.parts.push_back( placement );
z += weight->height;
}
if( z > s + rodlength ) { Result.warnings.push_back( "the stack is higher than the rod" ); }
return true;
}
void tensioner_rope_parts( tensioner_result &Result, std::string const &Cord, double const Length, double const Girth ) {
for( std::size_t k = 1; k < Result.rope.size(); ++k ) {
vec const from { Result.rope[ k - 1 ][ 0 ], Result.rope[ k - 1 ][ 1 ], Result.rope[ k - 1 ][ 2 ] };
vec const to { Result.rope[ k ][ 0 ], Result.rope[ k ][ 1 ], Result.rope[ k ][ 2 ] };
auto const span { length( to - from ) };
if( span < 1e-6 ) { continue; }
auto const z { unit( to - from ) };
auto const y { side_of( z ) };
auto placement { placed( "rope." + std::to_string( k ), Cord, from ) };
set_rotation( placement, cross( y, z ), y, z );
placement.scale[ 0 ] = Girth;
placement.scale[ 1 ] = Girth;
placement.scale[ 2 ] = Length > 1e-9 ? span / Length : 1.0;
Result.parts.push_back( placement );
}
}
} // namespace catenary