16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-22 08:09:19 +02:00

Reorganize source files into logical subdirectories

Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-14 19:01:57 +00:00
parent f981f81d55
commit 0531086bb9
221 changed files with 131 additions and 108 deletions

35
world/Curve.h Normal file
View File

@@ -0,0 +1,35 @@
/*
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/.
*/
#ifndef CurveH
#define CurveH
#include "QueryParserComp.hpp"
#include "usefull.h"
class TCurve
{
public:
TCurve();
~TCurve();
bool Init(int n, int c);
float GetValue(int c, float p);
bool SetValue(int c, float p, float v);
bool Load(TQueryParserComp *Parser);
bool LoadFromFile(AnsiString asName);
bool SaveToFile(AnsiString asName);
int iNumValues;
int iNumCols;
private:
float **Values;
};
//---------------------------------------------------------------------------
#endif

329
world/EvLaunch.cpp Normal file
View File

@@ -0,0 +1,329 @@
/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak and others
*/
#include "stdafx.h"
#include "EvLaunch.h"
#include "Globals.h"
#include "Logs.h"
#include "Event.h"
#include "MemCell.h"
#include "Timer.h"
#include "parser.h"
#include "Console.h"
#include "simulationtime.h"
#include "utilities.h"
//---------------------------------------------------------------------------
// encodes expected key in a short, where low byte represents the actual key,
// and the high byte holds modifiers: 0x1 = shift, 0x2 = ctrl, 0x4 = alt
int vk_to_glfw_key( int const Keycode ) {
char modifier = 0;
char key = 0;
if (Keycode < 'A') {
key = Keycode;
} else if (Keycode <= 'Z') {
key = Keycode;
modifier = GLFW_MOD_SHIFT;
} else if (Keycode < 'a') {
key = Keycode;
} else if (Keycode <= 'z') {
key = Keycode - 32;
} else {
ErrorLog("unknown key: " + std::to_string(Keycode));
}
return ((int)modifier << 8) | key;
}
bool TEventLauncher::Load(cParser *parser)
{ // wczytanie wyzwalacza zdarzeń
std::string token;
parser->getTokens();
*parser >> dRadius; // promień działania
if (dRadius > 0.0)
dRadius *= dRadius; // do kwadratu, pod warunkiem, że nie jest ujemne
parser->getTokens(); // klawisz sterujący
*parser >> token;
if (token != "none") {
if( token.size() == 1 ) {
// single char, assigned activation key
iKey = vk_to_glfw_key( token[ 0 ] );
}
else {
// this launcher may be activated by radio message
std::map<std::string, int> messages {
{ "radio_call1", radio_message::call1 },
{ "radio_call3", radio_message::call3 }
};
auto lookup = messages.find( token );
iKey = (
lookup != messages.end() ?
lookup->second :
// a jak więcej, to jakby numer klawisza jest
vk_to_glfw_key( stol_def( token, 0 ) ) );
}
}
parser->getTokens();
*parser >> DeltaTime;
parser->getTokens();
*parser >> token;
asEvent1Name = token; // pierwszy event
parser->getTokens();
*parser >> token;
asEvent2Name = token; // drugi event
if ((asEvent2Name == "end") || (asEvent2Name == "condition") || (asEvent2Name == "traintriggered"))
{ // drugiego eventu może nie być, bo są z tym problemy, ale ciii...
token = asEvent2Name; // rozpoznane słowo idzie do dalszego przetwarzania
asEvent2Name = "none"; // a drugiego eventu nie ma
}
else
{ // gdy są dwa eventy
parser->getTokens();
*parser >> token;
//str = AnsiString(token.c_str());
}
if (token == "condition")
{ // obsługa wyzwalania warunkowego
parser->getTokens();
*parser >> token;
asMemCellName = token;
parser->getTokens();
*parser >> token;
szText = token;
if (token != "*") //*=nie brać command pod uwagę
iCheckMask |= basic_event::flags::text;
parser->getTokens();
*parser >> token;
if (token != "*") //*=nie brać wartości 1. pod uwagę
{
iCheckMask |= basic_event::flags::value1;
fVal1 = atof(token.c_str());
}
else
fVal1 = 0;
parser->getTokens();
*parser >> token;
if (token.compare("*") != 0) //*=nie brać wartości 2. pod uwagę
{
iCheckMask |= basic_event::flags::value2;
fVal2 = atof(token.c_str());
}
else
fVal2 = 0;
parser->getTokens(); // słowo zamykające
*parser >> token;
}
if (token == "traintriggered")
{
train_triggered = true;
}
if( DeltaTime < 0 )
DeltaTime = -DeltaTime; // dla ujemnego zmieniamy na dodatni
else if( DeltaTime > 0 ) { // wartość dodatnia oznacza wyzwalanie o określonej godzinie
iMinute = int( DeltaTime ) % 100; // minuty są najmłodszymi cyframi dziesietnymi
iHour = int( DeltaTime - iMinute ) / 100; // godzina to setki
DeltaTime = 0; // bez powtórzeń
// potentially shift the provided time by requested offset
auto const timeoffset{ static_cast<int>( Global.ScenarioTimeOffset * 60 ) };
if( timeoffset != 0 ) {
auto const adjustedtime{ clamp_circular( iHour * 60 + iMinute + timeoffset, 24 * 60 ) };
iHour = ( adjustedtime / 60 ) % 24;
iMinute = adjustedtime % 60;
}
WriteLog(
"EventLauncher at "
+ std::to_string( iHour ) + ":"
+ ( iMinute < 10 ? "0" : "" ) + to_string( iMinute )
+ " (" + asEvent1Name
+ ( asEvent2Name != "none" ? " / " + asEvent2Name : "" )
+ ")" ); // wyświetlenie czasu
}
return true;
}
bool TEventLauncher::check_activation_key() {
if (iKey <= 0)
return false;
char key = iKey & 0xff;
bool result = Console::Pressed(key);
char modifier = iKey >> 8;
if (modifier & GLFW_MOD_SHIFT)
result &= Global.shiftState;
if (modifier & GLFW_MOD_CONTROL)
result &= Global.ctrlState;
return result;
}
bool TEventLauncher::check_activation() {
auto bCond { false };
if (DeltaTime == 10000.0) {
if (UpdatedTime == 0.0)
bCond = true;
UpdatedTime = 1.0;
}
else if( DeltaTime > 0 ) {
if( UpdatedTime > DeltaTime ) {
UpdatedTime = 0; // naliczanie od nowa
bCond = true;
}
else {
// aktualizacja naliczania czasu
UpdatedTime += Timer::GetDeltaTime();
}
}
else {
// jeśli nie cykliczny, to sprawdzić czas
if( simulation::Time.data().wHour == iHour ) {
if( simulation::Time.data().wMinute == iMinute ) {
// zgodność czasu uruchomienia
if( UpdatedTime < 10 ) {
UpdatedTime = 20; // czas do kolejnego wyzwolenia?
bCond = true;
}
}
}
else {
UpdatedTime = 1;
}
}
return bCond;
}
bool TEventLauncher::check_conditions() {
auto bCond { true };
if( ( iCheckMask != 0 )
&& ( MemCell != nullptr ) ) {
// sprawdzanie warunku na komórce pamięci
bCond = MemCell->Compare( szText, fVal1, fVal2, iCheckMask );
}
return bCond; // sprawdzanie dRadius w Ground.cpp
}
// sprawdzenie, czy jest globalnym wyzwalaczem czasu
bool TEventLauncher::IsGlobal() const {
return ( ( DeltaTime == 0 )
&& ( iHour >= 0 )
&& ( iMinute >= 0 )
&& ( dRadius < 0.0 ) ); // bez ograniczenia zasięgu
}
bool TEventLauncher::IsRadioActivated() const {
return ( iKey < 0 );
}
// radius() subclass details, calculates node's bounding radius
float
TEventLauncher::radius_() {
return std::sqrt( dRadius );
}
// serialize() subclass details, sends content of the subclass to provided stream
void
TEventLauncher::serialize_( std::ostream &Output ) const {
// TODO: implement
}
// deserialize() subclass details, restores content of the subclass from provided stream
void
TEventLauncher::deserialize_( std::istream &Input ) {
// TODO: implement
}
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void
TEventLauncher::export_as_text_( std::ostream &Output ) const {
// header
Output << "eventlauncher ";
// location
Output
<< location().x << ' '
<< location().y << ' '
<< location().z << ' ';
// activation radius
Output
<< ( dRadius > 0 ? std::sqrt( dRadius ) : dRadius ) << ' ';
// activation key
if( iKey != 0 ) {
auto key { iKey & 0xff };
auto const modifier { iKey >> 8 };
if (key >= 'A' && key <= 'Z' && !(modifier & GLFW_MOD_SHIFT))
key += 32;
Output << (char)key;
}
else {
Output << "none ";
}
// activation interval or hour
if( DeltaTime != 0.0 ) {
// cyclical launcher
Output << -DeltaTime << ' ';
}
else {
// single activation at specified time
if( ( iHour < 0 )
&& ( iMinute < 0 ) ) {
Output << DeltaTime << ' ';
}
else {
// NOTE: activation hour might be affected by user-requested time offset
auto const timeoffset{ static_cast<int>( Global.ScenarioTimeOffset * 60 ) };
auto const adjustedtime{ clamp_circular( iHour * 60 + iMinute - timeoffset, 24 * 60 ) };
Output
<< ( adjustedtime / 60 ) % 24
<< ( adjustedtime % 60 )
<< ' ';
}
}
// associated event(s)
Output << ( asEvent1Name.empty() ? "none" : asEvent1Name ) << ' ';
Output << ( asEvent2Name.empty() ? "none" : asEvent2Name ) << ' ';
if( false == asMemCellName.empty() ) {
// conditional event
Output
<< "condition "
<< asMemCellName << ' '
<< szText << ' '
<< ( ( iCheckMask & basic_event::flags::value1 ) != 0 ? to_string( fVal1 ) : "*" ) << ' '
<< ( ( iCheckMask & basic_event::flags::value2 ) != 0 ? to_string( fVal2 ) : "*" ) << ' ';
}
// footer
Output
<< "end"
<< "\n";
}
//---------------------------------------------------------------------------

75
world/EvLaunch.h Normal file
View File

@@ -0,0 +1,75 @@
/*
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/.
*/
#pragma once
#include <string>
#include "Classes.h"
#include "scenenode.h"
// radio-transmitted event launch messages
enum radio_message {
call3 = -3,
call1 = -1
};
class TEventLauncher : public scene::basic_node {
public:
// constructor
explicit TEventLauncher( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {}
// legacy constructor
TEventLauncher() = default;
// methods
bool Load( cParser *parser );
bool check_activation_key();
bool check_activation();
// checks conditions associated with the event. returns: true if the conditions are met
bool check_conditions();
inline
auto key() const {
return iKey; }
bool IsGlobal() const;
bool IsRadioActivated() const;
// members
std::string asEvent1Name;
std::string asEvent2Name;
std::string asMemCellName;
basic_event *Event1 { nullptr };
basic_event *Event2 { nullptr };
TMemCell *MemCell { nullptr };
int iCheckMask { 0 };
double dRadius { 0.0 };
bool train_triggered { false };
private:
// methods
// radius() subclass details, calculates node's bounding radius
float radius_();
// serialize() subclass details, sends content of the subclass to provided stream
void serialize_( std::ostream &Output ) const;
// deserialize() subclass details, restores content of the subclass from provided stream
void deserialize_( std::istream &Input );
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void export_as_text_( std::ostream &Output ) const;
// members
int iKey { 0 };
double DeltaTime { -1.0 };
double UpdatedTime { 0.0 };
double fVal1 { 0.0 };
double fVal2 { 0.0 };
std::string szText;
int iHour { -1 };
int iMinute { -1 }; // minuta uruchomienia
};
//---------------------------------------------------------------------------

2577
world/Event.cpp Normal file

File diff suppressed because it is too large Load Diff

753
world/Event.h Normal file
View File

@@ -0,0 +1,753 @@
/*
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/.
*/
#pragma once
#include "Classes.h"
#include "scene.h"
#include "Names.h"
#include "EvLaunch.h"
#include "Logs.h"
#include "command.h"
#include "comparison.h"
#ifdef WITH_LUA
#include "lua.h"
#endif
// common event interface
class basic_event {
public:
// types
enum flags {
// shared values
text = 1 << 0,
value1 = 1 << 1,
value2 = 1 << 2,
// update values
mode_add = 1 << 3,
// whois
mode_alt = 1 << 3,
whois_load = 1 << 4,
whois_name = 1 << 5,
// condition values
track_busy = 1 << 3,
track_free = 1 << 4,
probability = 1 << 5
};
// constructor
basic_event() = default;
// destructor
virtual ~basic_event();
// methods
// restores event data from provided stream
virtual
void
deserialize( cParser &Input, scene::scratch_data &Scratchpad );
// prepares event for use
virtual
void
init() = 0;
// executes event
virtual
void
run();
// sends basic content of the class in legacy (text) format to provided stream
virtual
void
export_as_text( std::ostream &Output ) const;
// adds a sibling event executed together
void
append( basic_event *Event );
// returns: true if the event should be executed immediately
virtual
bool
is_instant() const;
// sends content of associated data cell to specified vehicle controller
virtual
void
send_command( TController &Controller );
// returns: true if associated data cell contains a command for vehicle controller
virtual
bool
is_command() const;
// input data access
virtual std::string input_text() const;
virtual TCommandType input_command() const;
virtual double input_value( int Index ) const;
virtual glm::dvec3 input_location() const;
void group( scene::group_handle Group );
scene::group_handle group() const;
std::string const &name() const { return m_name; }
// members
basic_event *m_next { nullptr }; // następny w kolejce // TODO: replace with event list in the manager
basic_event *m_sibling { nullptr }; // kolejny event z tą samą nazwą - od wersji 378
std::string m_name;
bool m_ignored { false }; // replacement for tp_ignored
bool m_passive { false }; // false gdy ma nie być dodawany do kolejki (skanowanie sygnałów)
int m_inqueue { 0 }; // ile razy dodany do kolejki
TDynamicObject const *m_activator { nullptr };
double m_launchtime { 0.0 };
double m_delay { 0.0 };
double m_delayrandom { 0.0 }; // zakres dodatkowego opóźnienia // standardowo nie będzie dodatkowego losowego opóźnienia
double m_delaydeparture { std::numeric_limits<double>::quiet_NaN() }; // departure-based event delay
protected:
// types
using basic_node = std::tuple<std::string, scene::basic_node *>;
using node_sequence = std::vector<basic_node>;
struct event_conditions {
unsigned int flags { 0 };
float probability { 0.0 }; // used by conditional_probability
double memcompare_value1 { 0.0 }; // used by conditional_memcompare
double memcompare_value2 { 0.0 }; // used by conditional_memcompare
std::string memcompare_text; // used by conditional_memcompare
comparison_operator memcompare_value1_operator { comparison_operator::equal }; // used by conditional_memcompare
comparison_operator memcompare_value2_operator { comparison_operator::equal }; // used by conditional_memcompare
comparison_operator memcompare_text_operator { comparison_operator::equal }; // used by conditional_memcompare
comparison_pass memcompare_pass { comparison_pass::all }; // used by conditional_memcompare
basic_event::node_sequence *memcompare_cells; // used by conditional_memcompare
std::vector<TTrack *> tracks; // used by conditional_track
bool has_else { false };
void deserialize( cParser &Input );
void bind( basic_event::node_sequence *Nodes );
void init();
// verifies whether event meets execution condition(s)
bool test() const;
// sends basic content of the class in legacy (text) format to provided stream
void export_as_text( std::ostream &Output ) const;
};
// methods
template <class TableType_>
void init_targets( TableType_ &Repository, std::string const &Targettype, bool const Logerrors = true );
// returns true if provided token is a an event desription keyword, false otherwise
static bool is_keyword( std::string const &Token );
// members
node_sequence m_targets; // targets of operation performed when this event is executed
private:
// methods
// event type string
virtual std::string type() const = 0;
// deserialization helper, converts provided string to a list of target nodes
virtual void deserialize_targets( std::string const &Input );
// deserialize() subclass details
virtual void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) = 0;
// run() subclass details
virtual void run_() = 0;
// export_as_text() subclass details
virtual void export_as_text_( std::ostream &Output ) const = 0;
// members
scene::group_handle m_group { null_handle }; // group this event belongs to, if any
};
// specialized event, sends received input to its target(s)
// TBD: replace the generic module with specialized mixins
class input_event : public basic_event {
friend basic_event * make_event( cParser &Input, scene::scratch_data &Scratchpad );
protected:
// types
struct input_data {
unsigned int flags { 0 };
std::string data_text;
double data_value_1 { 0.0 };
double data_value_2 { 0.0 };
basic_node data_source { "", nullptr };
glm::dvec3 location { 0.0 };
TCommandType command_type { TCommandType::cm_Unknown };
TMemCell const * data_cell() const;
TMemCell * data_cell();
};
// members
input_data m_input;
};
class updatevalues_event : public input_event {
public:
// methods
// prepares event for use
void init() override;
// returns: true if the event should be executed immediately
bool is_instant() const override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
event_conditions m_conditions;
};
class copyvalues_event : public input_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
};
class getvalues_event : public input_event {
public:
// methods
// prepares event for use
void init() override;
// sends content of associated data cell to specified vehicle controller
void send_command( TController &Controller ) override;
// returns: true if associated data cell contains a command for vehicle controller
bool is_command() const override;
// input data access
std::string input_text() const override;
TCommandType input_command() const override;
double input_value( int Index ) const override;
glm::dvec3 input_location() const override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
};
class putvalues_event : public input_event {
public:
// methods
// prepares event for use
void init() override;
// input data access
std::string input_text() const override;
TCommandType input_command() const override;
double input_value( int Index ) const override;
glm::dvec3 input_location() const override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
//determines whether provided input should be passed to consist owner
bool is_command_for_owner( input_data const &Input ) const;
};
class whois_event : public input_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
};
class logvalues_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
};
class multi_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
std::vector<std::string> dump_children_names() const;
private:
// types
// wrapper for binding between editor-supplied name, event, and execution conditional flag
using conditional_event = std::tuple<std::string, basic_event *, bool>;
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
std::vector<conditional_event> m_children; // events which are placed in the query when this event is executed
event_conditions m_conditions;
};
class sound_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// types
// wrapper for binding between editor-supplied name and sound object
using basic_sound = std::tuple<std::string, sound_source *>;
// methods
// event type string
std::string type() const override;
// deserialization helper, converts provided string to a list of target nodes
void deserialize_targets( std::string const &Input ) override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
std::vector<basic_sound> m_sounds;
int m_soundmode{ 0 };
int m_soundradiochannel{ 0 };
};
// assigns a texture as specified replacable skin to a list of specified scene model nodes
// skin filename is built dynamically using specified expression and list of parameters from optional specified memory cell
class texture_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// types
struct input_data {
basic_node data_source { "", nullptr };
TMemCell const * data_cell() const;
TMemCell * data_cell();
};
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
int m_skinindex { 0 }; // index of target replacable skin
std::string m_skin; // expression defining skin filename
input_data m_input; // optional source of expression parameters
};
class animation_event : public basic_event {
public:
// destuctor
~animation_event() override;
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
int m_animationtype{ 0 };
std::array<double, 4> m_animationparams{ 0.0 };
std::string m_animationsubmodel;
std::list<std::weak_ptr<TAnimContainer>> m_animationcontainers;
std::string m_animationfilename;
std::size_t m_animationfilesize{ 0 };
char *m_animationfiledata{ nullptr };
};
class lights_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
std::vector<float> m_lights;
};
class switch_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
int m_switchstate{ 0 };
float m_switchmoverate{ -1.f };
float m_switchmovedelay{ -1.f };
};
class track_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
float m_velocity{ 0.f };
};
class voltage_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
float m_voltage{ -1.f };
};
class visible_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
bool m_visible{ true };
};
class friction_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
float m_friction{ -1.f };
};
#ifdef WITH_LUA
class lua_event : public basic_event {
public:
lua_event(lua::eventhandler_t func);
void init() override;
private:
std::string type() const override;
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
void run_() override;
void export_as_text_( std::ostream &Output ) const override;
bool is_instant() const override;
lua::eventhandler_t lua_func = nullptr;
};
#endif
class message_event : public basic_event {
public:
// methods
// prepares event for use
void init() override;
private:
// methods
// event type string
std::string type() const override;
// deserialize() subclass details
void deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) override;
// run() subclass details
void run_() override;
// export_as_text() subclass details
void export_as_text_( std::ostream &Output ) const override;
// members
std::string m_message;
};
basic_event *
make_event( cParser &Input, scene::scratch_data &Scratchpad );
class event_manager {
public:
// constructors
event_manager() = default;
// destructor
~event_manager();
// methods
// adds specified event launcher to the list of global launchers
void
queue( TEventLauncher *Launcher );
// inserts in the event query events assigned to event launchers capable of receiving specified radio message sent from specified location
void
queue_receivers( radio_message const Message, glm::dvec3 const &Location );
// legacy method, updates event queues
void
update();
// adds provided event to the collection. returns: true on success
// TBD, TODO: return handle to the event
bool
insert( basic_event *Event );
inline
bool
insert( TEventLauncher *Launcher ) {
return (
Launcher->IsRadioActivated() ?
m_radiodrivenlaunchers.insert( Launcher ) :
m_inputdrivenlaunchers.insert( Launcher ) ); }
inline void purge (TEventLauncher *Launcher) {
m_radiodrivenlaunchers.purge(Launcher);
m_inputdrivenlaunchers.purge(Launcher); }
// returns first event in the queue
inline
basic_event *
begin() {
return QueryRootEvent; }
basic_event*
FindEventById(uint32_t id);
uint32_t GetEventId(const basic_event *ev);
uint32_t GetEventId(std::string const &Name);
// legacy method, returns pointer to specified event, or null
basic_event *
FindEvent( std::string const &Name );
inline TEventLauncher* FindEventlauncher(std::string const &Name) {
auto ptr = m_inputdrivenlaunchers.find(Name);
return ptr ? ptr : m_radiodrivenlaunchers.find(Name);
}
// legacy method, inserts specified event in the event query
bool
AddToQuery( basic_event *Event, TDynamicObject const *Owner, double delay = 0.0 );
// legacy method, executes queued events
bool
CheckQuery();
// legacy method, initializes events after deserialization from scenario file
void
InitEvents();
// legacy method, initializes event launchers after deserialization from scenario file
void
InitLaunchers();
// sends basic content of the class in legacy (text) format to provided stream
void
export_as_text( std::ostream &Output ) const;
// returns all eventlaunchers in radius ignoring height
std::vector<TEventLauncher *>
find_eventlaunchers(glm::vec2 center, float radius) const;
private:
// types
using event_sequence = std::deque<basic_event *>;
using event_map = std::unordered_map<std::string, std::size_t>;
using eventlauncher_sequence = std::vector<TEventLauncher *>;
// members
event_sequence m_events;
/*
// NOTE: disabled until event class refactoring
event_sequence m_eventqueue;
*/
// legacy version of the above
basic_event *QueryRootEvent { nullptr };
basic_event *m_workevent { nullptr };
event_map m_eventmap;
basic_table<TEventLauncher> m_inputdrivenlaunchers;
basic_table<TEventLauncher> m_radiodrivenlaunchers;
eventlauncher_sequence m_launcherqueue;
command_relay m_relay;
};
inline
void
basic_event::group( scene::group_handle Group ) {
m_group = Group;
}
inline
scene::group_handle
basic_event::group() const {
return m_group;
}
template <class TableType_>
void
basic_event::init_targets( TableType_ &Repository, std::string const &Targettype, bool const Logerrors ) {
for( auto &target : m_targets ) {
std::get<scene::basic_node *>( target ) = Repository.find( std::get<std::string>( target ) );
if( std::get<scene::basic_node *>( target ) == nullptr ) {
m_ignored = true; // deaktywacja
if( Logerrors )
ErrorLog( "Bad event: \"" + m_name + "\" (type: " + type() + ") can't find " + Targettype +" \"" + std::get<std::string>( target ) + "\"" );
}
}
}
//---------------------------------------------------------------------------

283
world/MemCell.cpp Normal file
View File

@@ -0,0 +1,283 @@
/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak, Maciej Czapkiewicz and others
*/
#include "stdafx.h"
#include "MemCell.h"
#include "simulation.h"
#include "Driver.h"
#include "Event.h"
#include "Logs.h"
//---------------------------------------------------------------------------
TMemCell::TMemCell( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {}
void TMemCell::UpdateValues( std::string const &szNewText, double const fNewValue1, double const fNewValue2, int const CheckMask )
{
if (CheckMask & basic_event::flags::mode_add)
{ // dodawanie wartości
if( TestFlag( CheckMask, basic_event::flags::text ) )
szText += szNewText;
if (TestFlag(CheckMask, basic_event::flags::value1))
fValue1 += fNewValue1;
if (TestFlag(CheckMask, basic_event::flags::value2))
fValue2 += fNewValue2;
}
else
{
if( TestFlag( CheckMask, basic_event::flags::text ) )
szText = szNewText;
if (TestFlag(CheckMask, basic_event::flags::value1))
fValue1 = fNewValue1;
if (TestFlag(CheckMask, basic_event::flags::value2))
fValue2 = fNewValue2;
}
if (TestFlag(CheckMask, basic_event::flags::text))
CommandCheck(); // jeśli zmieniony tekst, próbujemy rozpoznać komendę
}
TCommandType TMemCell::CommandCheck()
{ // rozpoznanie komendy
if( szText == "SetVelocity" ) // najpopularniejsze
{
eCommand = TCommandType::cm_SetVelocity;
bCommand = false; // ta komenda nie jest wysyłana
}
else if( szText == "ShuntVelocity" ) // w tarczach manewrowych
{
eCommand = TCommandType::cm_ShuntVelocity;
bCommand = false; // ta komenda nie jest wysyłana
}
else if( szText == "Change_direction" ) // zdarza się
{
eCommand = TCommandType::cm_ChangeDirection;
bCommand = true; // do wysłania
}
else if( szText == "OutsideStation" ) // zdarza się
{
eCommand = TCommandType::cm_OutsideStation;
bCommand = false; // tego nie powinno być w komórce
}
else if( starts_with( szText, "PassengerStopPoint:" ) ) // porównanie początków
{
eCommand = TCommandType::cm_PassengerStopPoint;
bCommand = false; // tego nie powinno być w komórce
}
else if( szText == "SetProximityVelocity" ) // nie powinno tego być
{
eCommand = TCommandType::cm_SetProximityVelocity;
bCommand = false; // ta komenda nie jest wysyłana
}
else if( szText == "Emergency_brake" )
{
eCommand = TCommandType::cm_EmergencyBrake;
bCommand = false;
}
else if( szText == "CabSignal" ) {
eCommand = TCommandType::cm_SecuritySystemMagnet;
bCommand = false;
}
else
{
eCommand = TCommandType::cm_Unknown; // ciąg nierozpoznany (nie jest komendą)
bCommand = true; // do wysłania
}
return eCommand;
}
bool TMemCell::Load(cParser *parser)
{
std::string token;
parser->getTokens( 6, false );
*parser
>> m_area.center.x
>> m_area.center.y
>> m_area.center.z
>> szText
>> fValue1
>> fValue2;
parser->getTokens();
*parser >> token;
if (token != "none") // gdy różne od "none"
asTrackName = token; // sprawdzane przez IsEmpty()
parser->getTokens();
*parser >> token;
if (token != "endmemcell")
Error("endmemcell statement missing");
CommandCheck();
return true;
}
void TMemCell::PutCommand( TController *Mech, glm::dvec3 const *Loc ) const
{ // wysłanie zawartości komórki do AI
if (Mech)
Mech->PutCommand(szText, fValue1, fValue2, Loc);
}
bool TMemCell::Compare( std::string const &szTestText, double const fTestValue1, double const fTestValue2, int const CheckMask,
comparison_operator const TextOperator, comparison_operator const Value1Operator, comparison_operator const Value2Operator,
comparison_pass const Pass ) const {
// porównanie zawartości komórki pamięci z podanymi wartościami
/*
if( TestFlag( CheckMask, basic_event::flags::text ) ) {
// porównać teksty
auto range = szTestText.find( '*' );
if( range != std::string::npos ) {
// compare string parts
if( 0 != szText.compare( 0, range, szTestText, 0, range ) ) {
return false;
}
}
else {
// compare full strings
if( szText != szTestText ) {
return false;
}
}
}
// tekst zgodny, porównać resztę
return ( ( !TestFlag( CheckMask, basic_event::flags::value1 ) || ( fValue1 == fTestValue1 ) )
&& ( !TestFlag( CheckMask, basic_event::flags::value2 ) || ( fValue2 == fTestValue2 ) ) );
*/
bool checkpassed { false };
bool checkfailed { false };
if( TestFlag( CheckMask, basic_event::flags::text ) ) {
// porównać teksty
auto range = szTestText.find( '*' );
auto const result { (
range == std::string::npos ?
compare( szText, szTestText, TextOperator ) :
compare( szText.substr( 0, range ), szTestText.substr( 0, range ), TextOperator ) ) };
checkpassed |= result;
checkfailed |= ( !result );
}
if( TestFlag( CheckMask, basic_event::flags::value1 ) ) {
auto const result { compare( fValue1, fTestValue1, Value1Operator ) };
checkpassed |= result;
checkfailed |= ( !result );
}
if( TestFlag( CheckMask, basic_event::flags::value2 ) ) {
auto const result { compare( fValue2, fTestValue2, Value2Operator ) };
checkpassed |= result;
checkfailed |= ( !result );
}
switch( Pass ) {
case comparison_pass::all: { return ( checkfailed == false ); }
case comparison_pass::any: { return ( checkpassed == true ); }
case comparison_pass::none: { return ( checkpassed == false ); }
default: { return false; }
}
};
bool TMemCell::IsVelocity() const
{ // sprawdzenie, czy event odczytu tej komórki ma być do skanowania, czy do kolejkowania
if (eCommand == TCommandType::cm_SetVelocity)
return true;
if (eCommand == TCommandType::cm_ShuntVelocity)
return true;
return (eCommand == TCommandType::cm_SetProximityVelocity);
};
void TMemCell::StopCommandSent()
{ //
if (!bCommand)
return;
bCommand = false;
if( OnSent ) {
// jeśli jest event
simulation::Events.AddToQuery( OnSent, nullptr );
}
};
void TMemCell::AssignEvents(basic_event *e)
{ // powiązanie eventu
OnSent = e;
};
std::string TMemCell::Values() const {
return { "[" + Text() + "] [" + to_string( Value1(), 2 ) + "] [" + to_string( Value2(), 2 ) + "]" };
}
void TMemCell::LogValues() const {
WriteLog( "Memcell " + name() + ": " + Values() );
}
// serialize() subclass details, sends content of the subclass to provided stream
void
TMemCell::serialize_( std::ostream &Output ) const {
// TODO: implement
}
// deserialize() subclass details, restores content of the subclass from provided stream
void
TMemCell::deserialize_( std::istream &Input ) {
// TODO: implement
}
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void
TMemCell::export_as_text_( std::ostream &Output ) const {
// header
Output << "memcell ";
// location
Output
<< location().x << ' '
<< location().y << ' '
<< location().z << ' '
// cell data
<< szText << ' '
<< fValue1 << ' '
<< fValue2 << ' '
// associated track
<< ( Track ? Track->name() : asTrackName.empty() ? "none" : asTrackName ) << ' '
// footer
<< "endmemcell"
<< "\n";
}
// legacy method, initializes traction after deserialization from scenario file
void
memory_table::InitCells() {
for( auto *cell : m_items ) {
// Ra: eventy komórek pamięci, wykonywane po wysłaniu komendy do zatrzymanego pojazdu
cell->AssignEvents( simulation::Events.FindEvent( cell->name() + ":sent" ) );
// bind specified path with the memory cell
if( false == cell->asTrackName.empty() ) {
cell->Track = simulation::Paths.find( cell->asTrackName );
if( cell->Track == nullptr ) {
ErrorLog( "Bad memcell: track \"" + cell->asTrackName + "\" referenced in memcell \"" + cell->name() + "\" doesn't exist" );
}
}
}
}
// legacy method, sends content of all cells to the log
void
memory_table::log_all() {
for( auto *cell : m_items ) {
cell->LogValues();
}
}

94
world/MemCell.h Normal file
View File

@@ -0,0 +1,94 @@
/*
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/.
*/
#pragma once
#include "Classes.h"
#include "scenenode.h"
#include "Names.h"
#include "comparison.h"
class TMemCell : public scene::basic_node {
public:
// constructors
explicit TMemCell( scene::node_data const &Nodedata );
// methods
void
UpdateValues( std::string const &szNewText, double const fNewValue1, double const fNewValue2, int const CheckMask );
bool
Load(cParser *parser);
void
PutCommand( TController *Mech, glm::dvec3 const *Loc ) const;
bool
Compare( std::string const &szTestText, double const fTestValue1, double const fTestValue2, int const CheckMask,
comparison_operator const TextOperator = comparison_operator::equal,
comparison_operator const Value1Operator = comparison_operator::equal,
comparison_operator const Value2Operator = comparison_operator::equal,
comparison_pass const Pass = comparison_pass::all ) const;
std::string const &
Text() const {
return szText; }
double
Value1() const {
return fValue1; };
double
Value2() const {
return fValue2; };
TCommandType
Command() const {
return eCommand; };
bool
StopCommand() const {
return bCommand; };
void StopCommandSent();
TCommandType CommandCheck();
bool IsVelocity() const;
void AssignEvents(basic_event *e);
std::string Values() const;
void LogValues() const;
// members
std::string asTrackName; // McZapkie-100302 - zeby nazwe toru na ktory jest Putcommand wysylane pamietac
TTrack *Track { nullptr }; // resolved binding with the specified track
bool is_exportable { true }; // export helper; autogenerated cells don't need to be exported
private:
// methods
// serialize() subclass details, sends content of the subclass to provided stream
void serialize_( std::ostream &Output ) const;
// deserialize() subclass details, restores content of the subclass from provided stream
void deserialize_( std::istream &Input );
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void export_as_text_( std::ostream &Output ) const;
// members
// content
std::string szText;
double fValue1 { 0.0 };
double fValue2 { 0.0 };
// other
TCommandType eCommand { TCommandType::cm_Unknown };
bool bCommand { false }; // czy zawiera komendę dla zatrzymanego AI
basic_event *OnSent { nullptr }; // event dodawany do kolejki po wysłaniu komendy zatrzymującej skład
};
class memory_table : public basic_table<TMemCell> {
public:
// legacy method, initializes traction after deserialization from scenario file
void
InitCells();
// legacy method, sends content of all cells to the log
void
log_all();
};
//---------------------------------------------------------------------------

577
world/Segment.cpp Normal file
View File

@@ -0,0 +1,577 @@
/*
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 "Segment.h"
#include "Globals.h"
#include "Logs.h"
#include "parser.h"
#include "utilities.h"
#include "Track.h"
#include "renderer.h"
void
segment_data::deserialize( cParser &Input, glm::dvec3 const &Offset ) {
points[ segment_data::point::start ] = LoadPoint( Input ) + Offset;
Input.getTokens();
Input >> rolls[ 0 ];
points[ segment_data::point::control1 ] = LoadPoint( Input );
points[ segment_data::point::control2 ] = LoadPoint( Input );
points[ segment_data::point::end ] = LoadPoint( Input ) + Offset;
Input.getTokens( 2 );
Input
>> rolls[ 1 ]
>> radius;
}
TSegment::TSegment(TTrack *owner) :
pOwner( owner )
{}
bool TSegment::Init(Math3D::vector3 NewPoint1, Math3D::vector3 NewPoint2, double fNewStep, double fNewRoll1, double fNewRoll2)
{ // wersja dla prostego - wyliczanie punktów kontrolnych
Math3D::vector3 dir;
// NOTE: we're enforcing division also for straight track, to ensure dense enough mesh for per-vertex lighting
/*
if (fNewRoll1 == fNewRoll2)
{ // faktyczny prosty
dir = Normalize(NewPoint2 - NewPoint1); // wektor kierunku o długości 1
return TSegment::Init(NewPoint1, dir, -dir, NewPoint2, fNewStep, fNewRoll1, fNewRoll2,
false);
}
else
*/
{ // prosty ze zmienną przechyłką musi być segmentowany jak krzywe
dir = (NewPoint2 - NewPoint1) / 3.0; // punkty kontrolne prostego są w 1/3 długości
return TSegment::Init(
NewPoint1, NewPoint1 + dir,
NewPoint2 - dir, NewPoint2,
fNewStep, fNewRoll1, fNewRoll2, true);
}
};
bool TSegment::Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, Math3D::vector3 NewCPointIn, Math3D::vector3 &NewPoint2, double fNewStep, double fNewRoll1, double fNewRoll2, bool bIsCurve)
{ // wersja uniwersalna (dla krzywej i prostego)
Point1 = NewPoint1;
CPointOut = NewCPointOut;
CPointIn = NewCPointIn;
Point2 = NewPoint2;
// poprawienie przechyłki
fRoll1 = glm::radians(fNewRoll1); // Ra: przeliczone jest bardziej przydatne do obliczeń
fRoll2 = glm::radians(fNewRoll2);
bCurve = bIsCurve;
if (Global.bRollFix)
{ // Ra: poprawianie przechyłki
// Przechyłka powinna być na środku wewnętrznej szyny, a standardowo jest w osi
// toru. Dlatego trzeba podnieść tor oraz odpowiednio podwyższyć podsypkę.
// Nie wykonywać tej funkcji, jeśli podwyższenie zostało uwzględnione w edytorze.
// Problematyczne mogą byc rozjazdy na przechyłce - lepiej je modelować w edytorze.
// Na razie wszystkie scenerie powinny być poprawiane.
// Jedynie problem będzie z podwójną rampą przechyłkową, która w środku będzie
// mieć moment wypoziomowania, ale musi on być również podniesiony.
if (fRoll1 != 0.0)
{ // tylko jeśli jest przechyłka
double w1 = std::abs(std::sin(fRoll1) * 0.75); // 0.5*w2+0.0325; //0.75m dla 1.435
Point1.y += w1; // modyfikacja musi być przed policzeniem dalszych parametrów
if (bCurve)
CPointOut.y += w1; // prosty ma wektory jednostkowe
pOwner->MovedUp1(w1); // zwrócić trzeba informację o podwyższeniu podsypki
}
if (fRoll2 != 0.f)
{
double w2 = std::abs(std::sin(fRoll2) * 0.75); // 0.5*w2+0.0325; //0.75m dla 1.435
Point2.y += w2; // modyfikacja musi być przed policzeniem dalszych parametrów
if (bCurve)
CPointIn.y += w2; // prosty ma wektory jednostkowe
// zwrócić trzeba informację o podwyższeniu podsypki
}
}
// kąt w planie, żeby nie liczyć wielokrotnie
// Ra: ten kąt jeszcze do przemyślenia jest
fDirection = -std::atan2(Point2.x - Point1.x, Point2.z - Point1.z);
if (bCurve)
{ // przeliczenie współczynników wielomianu, będzie mniej mnożeń i można policzyć pochodne
vC = 3.0 * (CPointOut - Point1); // t^1
vB = 3.0 * (CPointIn - CPointOut) - vC; // t^2
vA = Point2 - Point1 - vC - vB; // t^3
fLength = ComputeLength();
}
else {
fLength = ( Point1 - Point2 ).Length();
}
if (fLength <= 0) {
ErrorLog( "Bad track: zero length spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" );
fLength = 0.01; // crude workaround TODO: fix this properly
}
fStoop = std::atan2((Point2.y - Point1.y), fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie
fStep = fNewStep;
// NOTE: optionally replace this part with the commented version, after solving geometry issues with double switches
if( ( pOwner->eType == tt_Switch )
&& ( fStep * ( 3.0 * Global.SplineFidelity ) > fLength ) ) {
// NOTE: a workaround for too short switches (less than 3 segments) messing up animation/generation of blades
fStep = fLength / ( 3.0 * Global.SplineFidelity );
}
// iSegCount = static_cast<int>( std::ceil( fLength / fStep ) ); // potrzebne do VBO
iSegCount = (
pOwner->eType == tt_Switch ?
6 * Global.SplineFidelity :
static_cast<int>( std::ceil( fLength / fStep ) ) ); // potrzebne do VBO
fStep = fLength / iSegCount; // update step to equalize size of individual pieces
fTsBuffer.resize( iSegCount + 1 );
fTsBuffer[ 0 ] = 0.0;
for( int i = 1; i < iSegCount; ++i ) {
fTsBuffer[ i ] = GetTFromS( i * fStep );
}
fTsBuffer[ iSegCount ] = 1.0;
return true;
}
Math3D::vector3 TSegment::GetFirstDerivative(double const fTime) const
{
double fOmTime = 1.0 - fTime;
double fPowTime = fTime;
Math3D::vector3 kResult = fOmTime * (CPointOut - Point1);
// int iDegreeM1 = 3 - 1;
double fCoeff = 2 * fPowTime;
kResult = (kResult + fCoeff * (CPointIn - CPointOut)) * fOmTime;
fPowTime *= fTime;
kResult += fPowTime * (Point2 - CPointIn);
kResult *= 3;
return kResult;
}
double TSegment::RombergIntegral(double const fA, double const fB) const
{
double fH = fB - fA;
const int ms_iOrder = 5;
double ms_apfRom[2][ms_iOrder];
ms_apfRom[0][0] =
0.5 * fH * ((GetFirstDerivative(fA).Length()) + (GetFirstDerivative(fB).Length()));
for (int i0 = 2, iP0 = 1; i0 <= ms_iOrder; i0++, iP0 *= 2, fH *= 0.5)
{
// approximations via the trapezoid rule
double fSum = 0.0;
int i1;
for (i1 = 1; i1 <= iP0; i1++)
fSum += (GetFirstDerivative(fA + fH * (i1 - 0.5)).Length());
// Richardson extrapolation
ms_apfRom[1][0] = 0.5 * (ms_apfRom[0][0] + fH * fSum);
for (int i2 = 1, iP2 = 4; i2 < i0; i2++, iP2 *= 4)
{
ms_apfRom[1][i2] = (iP2 * ms_apfRom[1][i2 - 1] - ms_apfRom[0][i2 - 1]) / (iP2 - 1);
}
for (i1 = 0; i1 < i0; i1++)
ms_apfRom[0][i1] = ms_apfRom[1][i1];
}
return ms_apfRom[0][ms_iOrder - 1];
}
double TSegment::GetTFromS(double const s) const
{
// initial guess for Newton's method
double fTolerance = 0.001;
double fRatio = s / RombergIntegral(0, 1);
double fTime = interpolate( 0.0, 1.0, fRatio );
int iteration = 0;
double fDifference {}; // exposed for debug down the road
do {
fDifference = RombergIntegral(0, fTime) - s;
if( std::abs( fDifference ) < fTolerance ) {
return fTime;
}
fTime -= fDifference / GetFirstDerivative(fTime).Length();
++iteration;
}
while( iteration < 10 ); // arbitrary limit
// Newton's method failed. If this happens, increase iterations or
// tolerance or integration accuracy.
// return -1; //Ra: tu nigdy nie dojdzie
ErrorLog( "Bad track: shape estimation failed for spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" );
// MessageBox(0,"Too many iterations","GetTFromS",MB_OK);
return fTime;
};
Math3D::vector3 TSegment::RaInterpolate(double const t) const
{ // wyliczenie XYZ na krzywej Beziera z użyciem współczynników
return t * (t * (t * vA + vB) + vC) + Point1; // 9 mnożeń, 9 dodawań
};
Math3D::vector3 TSegment::RaInterpolate0(double const t) const
{ // wyliczenie XYZ na krzywej Beziera, na użytek liczenia długości nie jest dodawane Point1
return t * (t * (t * vA + vB) + vC); // 9 mnożeń, 6 dodawań
};
double TSegment::ComputeLength() const // McZapkie-150503: dlugosc miedzy punktami krzywej
{ // obliczenie długości krzywej Beziera za pomocą interpolacji odcinkami
// Ra: zamienić na liczenie rekurencyjne średniej z cięciwy i łamanej po kontrolnych
// Ra: koniec rekurencji jeśli po podziale suma długości nie różni się więcej niż 0.5mm od
// poprzedniej
// Ra: ewentualnie rozpoznać łuk okręgu płaskiego i liczyć ze wzoru na długość łuku
double t, l = 0;
Math3D::vector3 last = Math3D::vector3(0, 0, 0); // długość liczona po przesunięciu odcinka do początku układu
Math3D::vector3 tmp = Point2 - Point1;
int m = 20.0 * tmp.Length(); // było zawsze do 10000, teraz jest liczone odcinkami po około 5cm
for (int i = 1; i <= m; i++)
{
t = double(i) / double(m); // wyznaczenie parametru na krzywej z przedziału (0,1>
// tmp=Interpolate(t,p1,cp1,cp2,p2);
tmp = RaInterpolate0(t); // obliczenie punktu dla tego parametru
t = Math3D::vector3(tmp - last).Length(); // obliczenie długości wektora
l += t; // zwiększenie wyliczanej długości
last = tmp;
}
return (l);
}
// finds point on segment closest to specified point in 3d space. returns: point on segment as value in range 0-1
double
TSegment::find_nearest_point( glm::dvec3 const &Point ) const {
if( ( false == bCurve ) || ( iSegCount == 1 ) ) {
// for straight track just treat it as a single segment
return
nearest_segment_point(
glm::dvec3{ FastGetPoint_0() },
glm::dvec3{ FastGetPoint_1() },
Point );
}
else {
// for curves iterate through segment chunks, and find the one which gives us the least distance to the specified point
double distance = std::numeric_limits<double>::max();
double nearest;
// NOTE: we're reusing already created segment chunks, which are created based on splinefidelity setting
// this means depending on splinefidelity the results can be potentially slightly different
for( int segmentidx = 0; segmentidx < iSegCount; ++segmentidx ) {
auto const segmentpoint =
clamp(
nearest_segment_point(
glm::dvec3{ FastGetPoint( fTsBuffer[ segmentidx ] ) },
glm::dvec3{ FastGetPoint( fTsBuffer[ segmentidx + 1 ] ) },
Point ) // point in range 0-1 on current segment
* ( fTsBuffer[ segmentidx + 1 ] - fTsBuffer[ segmentidx ] ) // segment length
+ fTsBuffer[ segmentidx ], // segment offset
0.0, 1.0 ); // we clamp the range in case there's some floating point math inaccuracies
auto const segmentdistance = glm::length2( Point - glm::dvec3{ FastGetPoint( segmentpoint ) } );
if( segmentdistance < distance ) {
nearest = segmentpoint;
distance = segmentdistance;
}
}
//
return nearest;
}
}
const double fDirectionOffset = 0.1; // długość wektora do wyliczenia kierunku
Math3D::vector3 TSegment::GetDirection(double const fDistance) const
{ // takie toporne liczenie pochodnej dla podanego dystansu od Point1
double t1 = GetTFromS(fDistance - fDirectionOffset);
if (t1 <= 0.0)
return (CPointOut - Point1); // na zewnątrz jako prosta
double t2 = GetTFromS(fDistance + fDirectionOffset);
if (t2 >= 1.0)
return (Point1 - CPointIn); // na zewnątrz jako prosta
return (FastGetPoint(t2) - FastGetPoint(t1));
}
Math3D::vector3 TSegment::FastGetDirection(double fDistance, double fOffset)
{ // takie toporne liczenie pochodnej dla parametru 0.0÷1.0
double t1 = fDistance - fOffset;
if (t1 <= 0.0)
return (CPointOut - Point1); // wektor na początku jest stały
double t2 = fDistance + fOffset;
if (t2 >= 1.0)
return (Point2 - CPointIn); // wektor na końcu jest stały
return (FastGetPoint(t2) - FastGetPoint(t1));
}
/*
Math3D::vector3 TSegment::GetPoint(double const fDistance) const
{ // wyliczenie współrzędnych XYZ na torze w odległości (fDistance) od Point1
if (bCurve)
{ // można by wprowadzić uproszczony wzór dla okręgów płaskich
double t = GetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera
// return Interpolate(t,Point1,CPointOut,CPointIn,Point2);
return RaInterpolate(t);
}
else {
// wyliczenie dla odcinka prostego jest prostsze
return
interpolate(
Point1, Point2,
clamp(
fDistance / fLength,
0.0, 1.0 ) );
}
};
*/
// ustalenie pozycji osi na torze, przechyłki, pochylenia i kierunku jazdy
void TSegment::RaPositionGet(double const fDistance, Math3D::vector3 &p, Math3D::vector3 &a) const {
if (bCurve) {
// można by wprowadzić uproszczony wzór dla okręgów płaskich
auto const t = GetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t)
p = FastGetPoint( t );
// przechyłka w danym miejscu (zmienia się liniowo)
a.x = interpolate<double>( fRoll1, fRoll2, t );
// pochodna jest 3*A*t^2+2*B*t+C
auto const tangent = t * ( t * 3.0 * vA + vB + vB ) + vC;
// pochylenie krzywej (w pionie)
a.y = std::atan( tangent.y );
// kierunek krzywej w planie
a.z = -std::atan2( tangent.x, tangent.z );
}
else {
// wyliczenie dla odcinka prostego jest prostsze
auto const t = fDistance / fLength; // zerowych torów nie ma
p = FastGetPoint( t );
// przechyłka w danym miejscu (zmienia się liniowo)
a.x = interpolate<double>( fRoll1, fRoll2, t );
a.y = fStoop; // pochylenie toru prostego
a.z = fDirection; // kierunek toru w planie
}
};
Math3D::vector3 TSegment::FastGetPoint(double const t) const
{
// return (bCurve?Interpolate(t,Point1,CPointOut,CPointIn,Point2):((1.0-t)*Point1+(t)*Point2));
return (
( ( true == bCurve ) || ( iSegCount != 1 ) ) ?
RaInterpolate( t ) :
interpolate( Point1, Point2, t ) );
}
bool TSegment::RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Origin, const gfx::vertex_array &ShapePoints, bool const Transition, double fTextureLength, double Texturescale, int iSkip, int iEnd, std::pair<float, float> fOffsetX, glm::vec3 **p, bool bRender)
{ // generowanie trójkątów dla odcinka trajektorii ruchu
// standardowo tworzy triangle_strip dla prostego albo ich zestaw dla łuku
// po modyfikacji - dla ujemnego (iNumShapePoints) w dodatkowych polach tabeli podany jest przekrój końcowy
if( fTsBuffer.empty() )
return false; // prowizoryczne zabezpieczenie przed wysypem - ustalić faktyczną przyczynę
glm::vec3 pos1, pos2, dir, parallel1, parallel2, pt, norm;
float s, step, fOffset, tv1, tv2, t, fEnd;
auto const iNumShapePoints = Transition ? ShapePoints.size() / 2 : ShapePoints.size();
float const texturelength = fTextureLength * Texturescale;
float const texturescale = Texturescale;
float m1, jmm1, m2, jmm2; // pozycje względne na odcinku 0...1 (ale nie parametr Beziera)
step = fStep;
tv1 = 1.0; // Ra: to by można było wyliczać dla odcinka, wyglądało by lepiej
s = fStep * iSkip; // iSkip - ile odcinków z początku pominąć
int i = iSkip; // domyślnie 0
t = fTsBuffer[ i ]; // tabela wattości t dla segmentów
// BUG: length of spline can be 0, we should skip geometry generation for such cases
fOffset = 0.1 / fLength; // pierwsze 10cm
pos1 = glm::dvec3{ FastGetPoint( t ) - Origin }; // wektor początku segmentu
dir = glm::dvec3{ FastGetDirection( t, fOffset ) }; // wektor kierunku
parallel1 = glm::vec3{ -dir.z, 0.f, dir.x }; // wektor poprzeczny
if( glm::length2( parallel1 ) == 0.f ) {
// temporary workaround for malformed situations with control points placed above endpoints
parallel1 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } };
}
parallel1 = glm::normalize( parallel1 );
if( iEnd == 0 )
iEnd = iSegCount;
fEnd = fLength * double( iEnd ) / double( iSegCount );
/*
m2 = s / fEnd;
*/
m2 = static_cast<float>( i - iSkip ) / ( iEnd - iSkip );
jmm2 = 1.f - m2;
while( i < iEnd ) {
++i; // kolejny punkt łamanej
s += step; // końcowa pozycja segmentu [m]
m1 = m2;
jmm1 = jmm2; // stara pozycja
/*
m2 = s / fEnd;
*/
m2 = static_cast<float>( i - iSkip ) / ( iEnd - iSkip );
jmm2 = 1.f - m2; // nowa pozycja
if( i == iEnd ) { // gdy przekroczyliśmy koniec - stąd dziury w torach...
step -= ( s - fEnd ); // jeszcze do wyliczenia mapowania potrzebny
s = fEnd;
m2 = 1.f;
jmm2 = 0.f;
}
/*
while( tv1 < 0.0 ) {
tv1 += 1.0;
}
*/
tv1 = clamp_circular( tv1, 1.0f );
tv2 = tv1 - step / texturelength; // mapowanie na końcu segmentu
t = fTsBuffer[ i ]; // szybsze od GetTFromS(s);
pos2 = glm::dvec3{ FastGetPoint( t ) - Origin };
dir = glm::dvec3{ FastGetDirection( t, fOffset ) }; // nowy wektor kierunku
parallel2 = glm::vec3{ -dir.z, 0.f, dir.x }; // wektor poprzeczny
if( glm::length2( parallel2 ) == 0.f ) {
// temporary workaround for malformed situations with control points placed above endpoints
parallel2 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } };
}
parallel2 = glm::normalize( parallel2 );
// TODO: refactor the loop, there's no need to calculate starting points for each segment when we can copy the end points of the previous one
if( Transition ) {
for( int j = 0; j < iNumShapePoints; ++j ) {
pt = parallel1 * ( jmm1 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m1 * ( ShapePoints[ j + iNumShapePoints ].position.x - fOffsetX.second ) ) + pos1;
pt.y += jmm1 * ShapePoints[ j ].position.y + m1 * ShapePoints[ j + iNumShapePoints ].position.y;
norm = ( jmm1 * ShapePoints[ j ].normal.x + m1 * ShapePoints[ j + iNumShapePoints ].normal.x ) * parallel1;
norm.y += jmm1 * ShapePoints[ j ].normal.y + m1 * ShapePoints[ j + iNumShapePoints ].normal.y;
if( bRender ) {
// skrzyżowania podczas łączenia siatek mogą nie renderować poboczy, ale potrzebować punktów
Output.emplace_back(
pt,
glm::normalize( norm ),
glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } );
}
if( p ) // jeśli jest wskaźnik do tablicy
if( *p )
if( !j ) // to dla pierwszego punktu
{
*( *p ) = pt;
( *p )++;
} // zapamiętanie brzegu jezdni
// dla trapezu drugi koniec ma inne współrzędne
pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j + iNumShapePoints ].position.x - fOffsetX.second ) ) + pos2;
pt.y += jmm2 * ShapePoints[ j ].position.y + m2 * ShapePoints[ j + iNumShapePoints ].position.y;
norm = ( jmm2 * ShapePoints[ j ].normal.x + m2 * ShapePoints[ j + iNumShapePoints ].normal.x ) * parallel2;
norm.y += jmm2 * ShapePoints[ j ].normal.y + m2 * ShapePoints[ j + iNumShapePoints ].normal.y;
if( bRender ) {
// skrzyżowania podczas łączenia siatek mogą nie renderować poboczy, ale potrzebować punktów
Output.emplace_back(
pt,
glm::normalize( norm ),
glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } );
}
if( p ) // jeśli jest wskaźnik do tablicy
if( *p )
if( !j ) // to dla pierwszego punktu
if( i == iSegCount ) {
*( *p ) = pt;
( *p )++;
} // zapamiętanie brzegu jezdni
}
}
else {
if( bRender ) {
for( int j = 0; j < iNumShapePoints; ++j ) {
//łuk z jednym profilem
pt = parallel1 * ( jmm1 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m1 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos1;
pt.y += ShapePoints[ j ].position.y;
norm = ShapePoints[ j ].normal.x * parallel1;
norm.y += ShapePoints[ j ].normal.y;
Output.emplace_back(
pt,
glm::normalize( norm ),
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } );
pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos2;
pt.y += ShapePoints[ j ].position.y;
norm = ShapePoints[ j ].normal.x * parallel2;
norm.y += ShapePoints[ j ].normal.y;
Output.emplace_back(
pt,
glm::normalize( norm ),
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } );
}
}
}
pos1 = pos2;
parallel1 = parallel2;
tv1 = tv2;
}
return true;
};
void TSegment::render_lines(std::vector<gfx::basic_vertex> &out, float quality) const
{
float step = 1.0f / iSegCount / quality;
float x;
glm::vec3 previous = FastGetPoint(0.0);
for (x = step; x <= 1.0f; x += step) {
out.push_back(gfx::basic_vertex(previous, glm::vec3(0.0f), glm::vec2(0.0f)));
previous = glm::vec3(FastGetPoint(x));
out.push_back(gfx::basic_vertex(previous, glm::vec3(0.0f), glm::vec2(0.0f)));
}
out.push_back(gfx::basic_vertex(previous, glm::vec3(0.0f), glm::vec2(0.0f)));
previous = glm::vec3(FastGetPoint(1.0));
out.push_back(gfx::basic_vertex(previous, glm::vec3(0.0f), glm::vec2(0.0f)));
}
glm::vec3 TSegment::get_nearest_point(const glm::dvec3 &point, float quality) const
{
float step = 1.0f / iSegCount / quality;
float x;
glm::vec3 nearest;
float min = std::numeric_limits<float>::max();
for (x = step; x <= 1.0f; x += step) {
glm::vec3 p1 = FastGetPoint(x);
glm::vec3 p2 = FastGetPoint(glm::min(1.0f, x + step));
if (p1 != p2) {
float l2 = glm::distance2(p1, p2);
float t = glm::max(0.0f, glm::min(1.0f, glm::dot((glm::vec3)point - p1, p2 - p1) / l2));
glm::vec3 proj = p1 + t * (p2 - p1);
p1 = proj;
}
float dist2 = glm::distance2(p1, (glm::vec3)point);
if (dist2 < min) {
nearest = p1;
min = dist2;
}
}
return nearest;
}

147
world/Segment.h Normal file
View File

@@ -0,0 +1,147 @@
/*
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/.
*/
#pragma once
#include "Classes.h"
#include "dumb3d.h"
#include "geometrybank.h"
#include "utilities.h"
struct map_colored_paths {
std::vector<gfx::geometrybank_handle> switches;
std::vector<gfx::geometrybank_handle> occupied;
std::vector<gfx::geometrybank_handle> future;
std::vector<gfx::geometrybank_handle> highlighted;
};
struct segment_data {
// types
enum point {
start = 0,
control1,
control2,
end
};
// members
std::array<glm::dvec3, 4> points {};
std::array<float, 2> rolls {};
float radius {};
// constructors
segment_data() = default;
// methods
void deserialize( cParser &Input, glm::dvec3 const &Offset );
};
class TSegment
{ // aproksymacja toru (zwrotnica ma dwa takie, jeden z nich jest aktywny)
private:
Math3D::vector3 Point1, CPointOut, CPointIn, Point2;
float
fRoll1 { 0.f },
fRoll2 { 0.f }; // przechyłka na końcach
double fLength { -1.0 }; // długość policzona
std::vector<double> fTsBuffer; // wartości parametru krzywej dla równych odcinków
double fStep = 0.0;
int iSegCount = 0; // ilość odcinków do rysowania krzywej
double fDirection = 0.0; // Ra: kąt prostego w planie; dla łuku kąt od Point1
double fStoop = 0.0; // Ra: kąt wzniesienia; dla łuku od Point1
Math3D::vector3 vA, vB, vC; // współczynniki wielomianów trzeciego stopnia vD==Point1
TTrack *pOwner = nullptr; // wskaźnik na właściciela
Math3D::vector3
GetFirstDerivative(double const fTime) const;
double
RombergIntegral(double const fA, double const fB) const;
double
GetTFromS(double const s) const;
Math3D::vector3
RaInterpolate(double const t) const;
Math3D::vector3
RaInterpolate0(double const t) const;
public:
bool bCurve = false;
TSegment(TTrack *owner);
bool
Init( Math3D::vector3 NewPoint1, Math3D::vector3 NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0);
bool
Init( Math3D::vector3 &NewPoint1, Math3D::vector3 NewCPointOut, Math3D::vector3 NewCPointIn, Math3D::vector3 &NewPoint2, double fNewStep, double fNewRoll1 = 0, double fNewRoll2 = 0, bool bIsCurve = true);
double
ComputeLength() const; // McZapkie-150503
// finds point on segment closest to specified point in 3d space. returns: point on segment as value in range 0-1
double
find_nearest_point( glm::dvec3 const &Point ) const;
inline
Math3D::vector3
GetDirection1() const {
return bCurve ? CPointOut - Point1 : CPointOut; };
inline
Math3D::vector3
GetDirection2() const {
return bCurve ? CPointIn - Point2 : CPointIn; };
Math3D::vector3
GetDirection(double const fDistance) const;
inline
Math3D::vector3
GetDirection() const {
return CPointOut; };
Math3D::vector3
FastGetDirection(double const fDistance, double const fOffset);
/*
Math3D::vector3
GetPoint(double const fDistance) const;
*/
void
RaPositionGet(double const fDistance, Math3D::vector3 &p, Math3D::vector3 &a) const;
Math3D::vector3
FastGetPoint(double const t) const;
inline
Math3D::vector3
FastGetPoint_0() const {
return Point1; };
inline
Math3D::vector3
FastGetPoint_1() const {
return Point2; };
inline
float
GetRoll(double const Distance) const {
return interpolate( fRoll1, fRoll2, static_cast<float>(Distance / fLength) ); }
inline
void
GetRolls(float &r1, float &r2) const {
// pobranie przechyłek (do generowania trójkątów)
r1 = fRoll1;
r2 = fRoll2; }
bool
RenderLoft( gfx::vertex_array &Output, Math3D::vector3 const &Origin, gfx::vertex_array const &ShapePoints, bool const Transition, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, std::pair<float, float> fOffsetX = {0.f, 0.f}, glm::vec3 **p = nullptr, bool bRender = true );
/*
void
Render();
*/
inline
double
GetLength() const {
return fLength; };
inline
int
RaSegCount() const {
return ( fTsBuffer.empty() ? 1 : iSegCount ); };
void
render_lines(std::vector<gfx::basic_vertex> &out, float quality = 1.0f) const;
glm::vec3
get_nearest_point(const glm::dvec3 &point, float quality = 1.0f) const;
};
//---------------------------------------------------------------------------

51
world/Spring.cpp Normal file
View File

@@ -0,0 +1,51 @@
/*
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 "Spring.h"
void TSpring::Init(double nKs, double nKd) {
Ks = nKs;
Kd = nKd;
ks = Ks;
kd = Kd;
}
Math3D::vector3 TSpring::ComputateForces( Math3D::vector3 const &pPosition1, Math3D::vector3 const &pPosition2) {
Math3D::vector3 springForce;
// p1 = &system[spring->p1];
// p2 = &system[spring->p2];
// VectorDifference(&p1->pos,&p2->pos,&deltaP); // Vector distance
auto deltaP = pPosition1 - pPosition2;
// dist = VectorLength(&deltaP); // Magnitude of deltaP
auto dist = deltaP.Length();
if( dist > restLen ) {
// Hterm = (dist - spring->restLen) * spring->Ks; // Ks * (dist - rest)
auto Hterm = ( dist - restLen ) * Ks; // Ks * (dist - rest)
// VectorDifference(&p1->v,&p2->v,&deltaV); // Delta Velocity Vector
auto deltaV = pPosition1 - pPosition2;
// Dterm = (DotProduct(&deltaV,&deltaP) * spring->Kd) / dist; // Damping Term
auto Dterm = (DotProduct(deltaV,deltaP) * Kd) / dist;
//Dterm = 0;
// ScaleVector(&deltaP,1.0f / dist, &springForce); // Normalize Distance Vector
// ScaleVector(&springForce,-(Hterm + Dterm),&springForce); // Calc Force
springForce = deltaP / dist * ( -( Hterm + Dterm ));
// VectorSum(&p1->f,&springForce,&p1->f); // Apply to Particle 1
// VectorDifference(&p2->f,&springForce,&p2->f); // - Force on Particle 2
}
return springForce;
}
//---------------------------------------------------------------------------

42
world/Spring.h Normal file
View File

@@ -0,0 +1,42 @@
/*
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/.
*/
#ifndef ParticlesH
#define ParticlesH
#include "dumb3d.h"
/*
#define STATIC_THRESHOLD 0.17f
const double m_Kd = 0.02f; // DAMPING FACTOR
const double m_Kr = 0.8f; // 1.0 = SUPERBALL BOUNCE 0.0 = DEAD WEIGHT
const double m_Ksh = 5.0f; // HOOK'S SPRING CONSTANT
const double m_Ksd = 0.1f; // SPRING DAMPING CONSTANT
const double m_Csf = 0.9f; // Default Static Friction
const double m_Ckf = 0.7f; // Default Kinetic Friction
*/
class TSpring {
public:
TSpring() = default;
// void Init(TParticnp1, TParticle *np2, double nKs= 0.5f, double nKd= 0.002f,
// double nrestLen= -1.0f);
void Init(double nKs = 0.5f, double nKd = 0.002f);
Math3D::vector3 ComputateForces( Math3D::vector3 const &pPosition1, Math3D::vector3 const &pPosition2);
//private:
// members
double restLen { 0.01 }; // LENGTH OF SPRING AT REST
double Ks { 0.0 }; // SPRING CONSTANT
double Kd { 0.0 }; // SPRING DAMPING
float ks{ 0.f };
float kd{ 0.f };
};
//---------------------------------------------------------------------------
#endif

3622
world/Track.cpp Normal file

File diff suppressed because it is too large Load Diff

375
world/Track.h Normal file
View File

@@ -0,0 +1,375 @@
/*
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/.
*/
#pragma once
#include <string>
#include <vector>
#include <deque>
#include "Classes.h"
#include "Segment.h"
#include "material.h"
#include "scenenode.h"
#include "Names.h"
namespace scene {
class basic_cell;
}
enum TTrackType {
tt_Unknown,
tt_Normal,
tt_Switch,
tt_Table,
tt_Cross,
tt_Tributary
};
// McZapkie-100502
enum TEnvironmentType {
e_unknown = -1,
e_flat = 0,
e_mountains,
e_canyon,
e_tunnel,
e_bridge,
e_bank
};
// Ra: opracować alternatywny system cieni/świateł z definiowaniem koloru oświetlenia w halach
class basic_event;
class TTrack;
class TGroundNode;
class TSubRect;
class TTraction;
class TSwitchExtension
{ // dodatkowe dane do toru, który jest zwrotnicą
public:
TSwitchExtension(TTrack *owner, int const what);
~TSwitchExtension();
std::shared_ptr<TSegment> Segments[6]; // dwa tory od punktu 1, pozosta³e dwa od 2? Ra 140101: 6 po³¹czeñ dla skrzy¿owañ
TTrack *pNexts[2]; // tory do³¹czone do punktów 2 i 4
TTrack *pPrevs[2]; // tory do³¹czone do punktów 1 i 3
int iNextDirection[2]; // to te¿ z [2]+[2] przerobiæ na [4]
int iPrevDirection[2];
int CurrentIndex = 0; // dla zwrotnicy
float
fOffset{ 0.f },
fDesiredOffset{ 0.f }; // aktualne i docelowe położenie napędu iglic
float fOffsetSpeed = 0.1f; // prędkość liniowa ruchu iglic
float fOffsetDelay = 0.05f; // opóźnienie ruchu drugiej iglicy względem pierwszej // dodatkowy ruch drugiej iglicy po zablokowaniu pierwszej na opornicy
union
{
struct
{ // zmienne potrzebne tylko dla zwrotnicy
float fOffset1,
fOffset2; // przesunięcia iglic - 0=na wprost
bool RightSwitch; // czy zwrotnica w prawo
};
struct
{ // zmienne potrzebne tylko dla obrotnicy/przesuwnicy
// TAnimContainer *pAnim; //animator modelu dla obrotnicy
TAnimModel *pModel; // na razie model
};
struct
{ // zmienne dla skrzyżowania
int iRoads; // ile dróg się spotyka?
glm::vec3 *vPoints; // tablica wierzchołków nawierzchni, generowana przez pobocze
bool bPoints; // czy utworzone?
};
};
bool bMovement = false; // czy w trakcie animacji
scene::basic_cell *pOwner = nullptr; // TODO: convert this to observer pattern
TTrack *pNextAnim = nullptr; // następny tor do animowania
basic_event *evPlus = nullptr,
*evMinus = nullptr; // zdarzenia sygnalizacji rozprucia
float fVelocity = -1.0; // maksymalne ograniczenie prędkości (ustawianej eventem)
Math3D::vector3 vTrans; // docelowa translacja przesuwnicy
material_handle m_material3 = 0; // texture of auto generated switch trackbed
gfx::geometry_handle Geometry3; // geometry of auto generated switch trackbed
gfx::geometrybank_handle map_geometry[6]; // geometry for map visualisation
};
class TIsolated
{ // obiekt zbierający zajętości z kilku odcinków
public:
// constructors
TIsolated(const std::string &n, TIsolated *i);
// methods
static void DeleteAll();
static TIsolated * Find(const std::string &n); // znalezienie obiektu albo utworzenie nowego
void AssignEvents();
void Modify(int i, TDynamicObject *o); // dodanie lub odjęcie osi
inline
bool
Busy() {
return (iAxles > 0); };
inline
static TIsolated *
Root() {
return (pRoot); };
inline
TIsolated *
Next() {
return (pNext); };
inline
void
parent( TIsolated *Parent ) {
pParent = Parent; }
inline
TIsolated *
parent() const {
return pParent; }
// members
std::string asName; // nazwa obiektu, baza do nazw eventów
basic_event *evBusy { nullptr }; // zdarzenie wyzwalane po zajęciu grupy
basic_event *evFree { nullptr }; // zdarzenie wyzwalane po całkowitym zwolnieniu zajętości grupy
basic_event *evInc { nullptr }; // wyzwalane po wjeździe na grupę
basic_event *evDec { nullptr }; // wyzwalane po zjazdu z grupy
TMemCell *pMemCell { nullptr }; // automatyczna komórka pamięci, która współpracuje z odcinkiem izolowanym
private:
// members
int iAxles { 0 }; // ilość osi na odcinkach obsługiwanych przez obiekt
TIsolated *pNext { nullptr }; // odcinki izolowane są trzymane w postaci listy jednikierunkowej
TIsolated *pParent { nullptr }; // optional parent piece, receiving data from its children
static TIsolated *pRoot; // początek listy
};
// trajektoria ruchu - opakowanie
class TTrack : public scene::basic_node
{
friend opengl_renderer;
friend opengl33_renderer;
// NOTE: temporary arrangement
friend itemproperties_panel;
public:
std::vector<TIsolated*> Isolated; // obwód izolowany obsługujący zajęcia/zwolnienia grupy torów
std::shared_ptr<TSwitchExtension> SwitchExtension; // dodatkowe dane do toru, który jest zwrotnicą
std::shared_ptr<TSegment> Segment;
TTrack * trNext = nullptr; // odcinek od strony punktu 2 - to powinno być w segmencie
TTrack * trPrev = nullptr; // odcinek od strony punktu 1
// McZapkie-070402: dodalem zmienne opisujace rozmiary tekstur
int iTrapezoid = 0; // 0-standard, 1-przechyłka, 2-trapez, 3-oba
double fRadiusTable[ 2 ] = { 0.0, 0.0 }; // dwa promienie, drugi dla zwrotnicy
float fVerticalRadius { 0.f }; // y-axis track radius (currently not supported)
float fTexLength = 4.0f; // długość powtarzania tekstury w metrach
float fTexRatio1 = 1.0f; // proporcja boków tekstury nawierzchni (żeby zaoszczędzić na rozmiarach tekstur...)
float fTexRatio2 = 1.0f; // proporcja boków tekstury chodnika (żeby zaoszczędzić na rozmiarach tekstur...)
float fTexHeight1 = 0.6f; // wysokość brzegu względem trajektorii
float fTexHeightOffset = 0.f; // potential adjustment to account for the path roll
float fTexWidth = 0.9f; // szerokość boku
float fTexSlope = 0.9f;
glm::dvec3 m_origin;
// TODO: store material names as strings, for lossless serialization and export
material_handle m_material1 = 0; // tekstura szyn albo nawierzchni
material_handle m_material2 = 0; // tekstura automatycznej podsypki albo pobocza
std::pair<std::string, int> m_profile1 {}; // profile of geometry chunks textured with texture 1
using geometryhandle_sequence = std::vector<gfx::geometry_handle>;
geometryhandle_sequence Geometry1; // geometry chunks textured with texture 1
geometryhandle_sequence Geometry2; // geometry chunks textured with texture 2
std::vector<segment_data> m_paths; // source data for owned paths
int iterate_stamp = 0;
public:
using dynamics_sequence = std::deque<TDynamicObject *>;
using event_sequence = std::vector<std::pair<std::string, basic_event *> >;
dynamics_sequence Dynamics;
event_sequence
m_events0all,
m_events1all,
m_events2all,
m_events0,
m_events1,
m_events2;
bool m_events { false }; // Ra: flaga informująca o obecności eventów
std::pair<std::string, TMemCell *> m_friction { "", nullptr };
int iNextDirection = 0; // 0:Point1, 1:Point2, 3:do odchylonego na zwrotnicy
int iPrevDirection = 0; // domyślnie wirtualne odcinki dołączamy stroną od Point1
TTrackType eType = tt_Normal; // domyślnie zwykły
int iCategoryFlag = 1; // 0x100 - usuwanie pojazów // 1-tor, 2-droga, 4-rzeka, 8-samolot?
float fTrackWidth = 1.435f; // szerokość w punkcie 1 // rozstaw toru, szerokość nawierzchni
float fTrackWidth2 = 0.0f; // szerokość w punkcie 2 (głównie drogi i rzeki)
float fFriction = 0.15f; // współczynnik tarcia
float fSoundDistance = -1.0f;
int iQualityFlag = 20;
int iDamageFlag = 0;
TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie
int iAction = 0; // czy modyfikowany eventami (specjalna obsługa przy skanowaniu)
float fOverhead = -1.0; // można normalnie pobierać prąd (0 dla jazdy bezprądowej po danym odcinku, >0-z opuszczonym i ograniczeniem prędkości)
private:
double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia)
public:
// McZapkie-100502:
// double fTrackLength = 100.0; // długość z wpisu, nigdzie nie używana
double fRadius = 0.0; // promień, dla zwrotnicy kopiowany z tabeli
bool ScannedFlag = false; // McZapkie: do zaznaczania kolorem torów skanowanych przez AI
TGroundNode *nFouling[ 2 ] = { nullptr, nullptr }; // współrzędne ukresu albo oporu kozła
explicit TTrack( scene::node_data const &Nodedata );
virtual ~TTrack();
void Init();
static bool sort_by_material( TTrack const *Left, TTrack const *Right );
static TTrack * Create400m(int what, double dx);
TTrack * NullCreate(int dir);
inline bool IsEmpty() {
return Dynamics.empty(); };
void ConnectPrevPrev(TTrack *pNewPrev, int typ);
void ConnectPrevNext(TTrack *pNewPrev, int typ);
void ConnectNextPrev(TTrack *pNewNext, int typ);
void ConnectNextNext(TTrack *pNewNext, int typ);
material_handle copy_adjacent_trackbed_material( TTrack const *Exclude = nullptr );
inline double Length() const {
return Segment->GetLength(); };
inline std::shared_ptr<TSegment> CurrentSegment() const {
return Segment; };
inline TTrack *CurrentNext() const {
return trNext; };
inline TTrack *CurrentPrev() const {
return trPrev; };
TTrack *Connected(int s, double &d) const;
bool SetConnections(int i);
bool Switch(int i, float const t = -1.f, float const d = -1.f);
bool SwitchForced(int i, TDynamicObject *o);
int CrossSegment(int from, int into);
inline int GetSwitchState() {
return (
SwitchExtension ?
SwitchExtension->CurrentIndex :
-1); };
// returns number of different routes possible to take from given point
// TODO: specify entry point, number of routes for switches can vary
inline
int
RouteCount() const {
return (
SwitchExtension != nullptr ?
SwitchExtension->iRoads - 1 :
1 ); }
void Load(cParser *parser, glm::dvec3 const &pOrigin);
bool AssignEvents();
bool AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus);
void QueueEvents( event_sequence const &Events, TDynamicObject const *Owner );
void QueueEvents( event_sequence const &Events, TDynamicObject const *Owner, double const Delaylimit );
bool CheckDynamicObject(TDynamicObject *Dynamic);
bool AddDynamicObject(TDynamicObject *Dynamic);
bool RemoveDynamicObject(TDynamicObject *Dynamic);
// set origin point
void
origin( glm::dvec3 Origin ) {
m_origin = Origin; }
// retrieves list of the track's end points
std::vector<glm::dvec3>
endpoints() const;
gfx::geometrybank_handle extra_map_geometry; // handle for map highlighting
TTrack *Next(TTrack *visitor);
double ActiveLength();
void create_geometry( gfx::geometrybank_handle const &Bank ); // wypełnianie VBO
void create_map_geometry(std::vector<gfx::basic_vertex> &Bank, const gfx::geometrybank_handle Extra);
void get_map_active_paths(map_colored_paths &handles);
void get_map_paths_for_state(map_colored_paths &handles, int state);
void get_map_future_paths(map_colored_paths &handles);
glm::vec3 get_nearest_point(const glm::dvec3 &point) const;
void RenderDynSounds(); // odtwarzanie dźwięków pojazdów jest niezależne od ich wyświetlania
void RaOwnerSet( scene::basic_cell *o ) {
if( SwitchExtension ) { SwitchExtension->pOwner = o; } };
bool InMovement(); // czy w trakcie animacji?
void RaAssign( TAnimModel *am, basic_event *done, basic_event *joined );
void RaAnimListAdd(TTrack *t);
TTrack * RaAnimate();
void RadioStop();
void AxleCounter(int i, TDynamicObject *o) {
for (TIsolated *iso : Isolated)
iso->Modify(i, o); }; // dodanie lub odjęcie osi
std::string RemoteIsolatedName();
void AddIsolated(TIsolated *iso) {
Isolated.push_back(iso);
}
double WidthTotal();
bool IsGroupable();
int TestPoint( Math3D::vector3 *Point);
void MovedUp1(float const dh);
void VelocitySet(float v);
double VelocityGet();
float Friction() const;
void ConnectionsLog();
bool DoubleSlip() const;
static void fetch_default_profiles();
std::string tooltip() const override;
private:
// types
using profiles_array = std::vector<gfx::vertex_array>;
using profiles_map = std::unordered_map<std::string, int>;
// methods
// radius() subclass details, calculates node's bounding radius
float radius_();
// serialize() subclass details, sends content of the subclass to provided stream
void serialize_( std::ostream &Output ) const;
// deserialize() subclass details, restores content of the subclass from provided stream
void deserialize_( std::istream &Input );
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void export_as_text_( std::ostream &Output ) const;
// locates specified profile in the profile database, potentially loading it from a file
static std::pair<std::string, int> fetch_track_rail_profile( std::string const &Profile );
// loads content of specified file and converts it into a vertex array
static gfx::vertex_array deserialize_profile( std::string const &Profile );
// provides direct access to vertex data of specified profile
static gfx::vertex_array const & track_rail_profile( int const Profile );
// returns texture length for specified material
float texture_length( material_handle const Material );
// creates profile for a part of current path
void create_switch_trackbed( gfx::vertex_array &Output );
void create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left );
void create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array &Left );
void create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next );
void create_road_profile( gfx::vertex_array &Output, bool const Forcetransition = false );
void create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false );
// members
static profiles_array m_profiles; // shared database of path element profiles
static profiles_map m_profilesmap;
};
// collection of virtual tracks and roads present in the scene
class path_table : public basic_table<TTrack> {
public:
~path_table();
// legacy method, initializes tracks after deserialization from scenario file
void
InitTracks();
// legacy method, sends list of occupied paths over network
void
TrackBusyList() const;
// legacy method, sends list of occupied path sections over network
void
IsolatedBusyList() const;
// legacy method, sends state of specified path section over network
void
IsolatedBusy( std::string const &Name ) const;
};
//---------------------------------------------------------------------------

902
world/Traction.cpp Normal file
View File

@@ -0,0 +1,902 @@
/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak, Maciej Czapkiewicz and others
*/
#include "stdafx.h"
#include "Traction.h"
#include "simulation.h"
#include "Globals.h"
#include "TractionPower.h"
#include "Logs.h"
#include "renderer.h"
#include "utilities.h"
//---------------------------------------------------------------------------
/*
=== Koncepcja dwustronnego zasilania sekcji sieci trakcyjnej, Ra 2014-02 ===
0. Każde przęsło sieci może mieć wpisaną nazwę zasilacza, a także napięcie
nominalne i maksymalny prąd, które stanowią redundancję do danych zasilacza.
Rezystancja może się zmieniać, materiał i grubość drutu powinny być wspólny
dla segmentu. Podanie punktów umożliwia łączenie przęseł w listy dwukierunkowe,
co usprawnia wyszukiwania kolejnych przeseł podczas jazdy. Dla bieżni wspólnej
powinna być podana nazwa innego przęsła w parametrze "parallel". Wskaźniki
na przęsła bieżni wspólnej mają być układane w jednokierunkowy pierścień.
1. Problemem jest ustalenie topologii sekcji dla linii dwutorowych. Nad każdym
torem powinna znajdować się oddzielna sekcja sieci, aby mogła zostać odłączona
w przypadku zwarcia. Sekcje nad równoległymi torami są również łączone
równolegle przez kabiny sekcyjne, co zmniejsza płynące prądy i spadki napięć.
2. Drugim zagadnieniem jest zasilanie sekcji jednocześnie z dwóch stron, czyli
sekcja musi mieć swoją nazwę oraz wskazanie dwóch zasilaczy ze wskazaniem
geograficznym ich położenia. Dodatkową trudnością jest brak połączenia
pomiędzy segmentami naprężania. Podsumowując, każdy segment naprężania powinien
mieć przypisanie do sekcji zasilania, a dodatkowo skrajne segmenty powinny
wskazywać dwa różne zasilacze.
3. Zasilaczem sieci może być podstacja, która w wersji 3kV powinna generować
pod obciążeniem napięcie maksymalne rzędu 3600V, a spadek napięcia następuje
na jej rezystancji wewnętrznej oraz na przewodach trakcyjnych. Zasilaczem może
być również kabina sekcyjna, która jest zasilana z podstacji poprzez przewody
trakcyjne.
4. Dla uproszczenia można przyjąć, że zasilanie pojazdu odbywać się będzie z
dwóch sąsiednich podstacji, pomiędzy którymi może być dowolna liczba kabin
sekcyjnych. W przypadku wyłączenia jednej z tych podstacji, zasilanie może
być pobierane z kolejnej. Łącznie należy rozważać 4 najbliższe podstacje,
przy czym do obliczeń można przyjmować 2 z nich.
5. Przęsła sieci są łączone w listę dwukierunkową, więc wystarczy nazwę
sekcji wpisać w jednym z nich, wpisanie w każdym nie przeszkadza.
Alternatywnym sposobem łączenia segmentów naprężania może być wpisywanie
nazw przęseł jako "parallel", co może być uciążliwe dla autorów scenerii.
W skrajnych przęsłach należałoby dodatkowo wpisać nazwę zasilacza, będzie
to jednocześnie wskazanie przęsła, do którego podłączone są przewody
zasilające. Konieczne jest odróżnienie nazwy sekcji od nazwy zasilacza, co
można uzyskać różnicując ich nazwy albo np. specyficznie ustawiając wartość
prądu albo napięcia przęsła.
6. Jeśli dany segment naprężania jest wspólny dla dwóch sekcji zasilania,
to jedno z przęseł musi mieć nazwę "*" (gwiazdka), co będzie oznaczało, że
ma zamontowany izolator. Dla uzyskania efektów typu łuk elektryczny, należało
by wskazać położenie izolatora i jego długość (ew. typ).
7. Również w parametrach zasilacza należało by określić, czy jest podstacją,
czy jedynie kabiną sekcyjną. Różnić się one będą fizyką działania.
8. Dla zbudowanej topologii sekcji i zasilaczy należało by zbudować dynamiczny
schemat zastępczy. Dynamika polega na wyłączaniu sekcji ze zwarciem oraz
przeciążonych podstacji. Musi być też możliwość wyłączenia sekcji albo
podstacji za pomocą eventu.
9. Dla każdej sekcji musi być tworzony obiekt, wskazujący na podstacje
zasilające na końcach, stan włączenia, zwarcia, przepięcia. Do tego obiektu
musi wskazywać każde przęsło z aktywnym zasilaniem.
z.1 z.2 z.3
-=-a---1*1---c-=---c---=-c--2*2--e---=---e-3-*-3--g-=-
-=-b---1*1---d-=---d---=-d--2*2--f---=---e-3-*-3--h-=-
nazwy sekcji (@): a,b,c,d,e,f,g,h
nazwy zasilaczy (#): 1,2,3
przęsło z izolatorem: *
przęsła bez wskazania nazwy sekcji/zasilacza: -
segment napręzania: =-x-=
segment naprężania z izolatorem: =---@---#*#---@---=
segment naprężania bez izolatora: =--------@------=
Obecnie brak nazwy sekcji nie jest akceptowany i każde przęsło musi mieć wpisaną
jawnie nazwę sekcji, ewentualnie nazwę zasilacza (zostanie zastąpiona wskazaniem
sekcji z sąsiedniego przęsła).
*/
TTraction::TTraction( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {}
void
TTraction::Load( cParser *parser, glm::dvec3 const &pOrigin ) {
parser->getTokens( 4 );
*parser
>> asPowerSupplyName
>> NominalVoltage
>> MaxCurrent
>> fResistivity;
if( fResistivity == 0.01f ) {
// tyle jest w sceneriach [om/km]
// taka sensowniejsza wartość za http://www.ikolej.pl/fileadmin/user_upload/Seminaria_IK/13_05_07_Prezentacja_Kruczek.pdf
fResistivity = 0.075f;
}
fResistivity *= 0.001f; // teraz [om/m]
// Ra 2014-02: a tutaj damy symbol sieci i jej budowę, np.:
// SKB70-C, CuCd70-2C, KB95-2C, C95-C, C95-2C, YC95-2C, YpC95-2C, YC120-2C
// YpC120-2C, YzC120-2C, YwsC120-2C, YC150-C150, YC150-2C150, C150-C150
// C120-2C, 2C120-2C, 2C120-2C-1, 2C120-2C-2, 2C120-2C-3, 2C120-2C-4
auto const material = parser->getToken<std::string>();
// 1=miedziana, rysuje się na zielono albo czerwono
// 2=aluminiowa, rysuje się na czarno
if( material == "none" ) { Material = 0; }
else if( material == "al" ) { Material = 2; }
else { Material = 1; }
parser->getTokens( 2 );
*parser
>> WireThickness
>> DamageFlag;
pPoint1 = LoadPoint( *parser ) + pOrigin;
pPoint2 = LoadPoint( *parser ) + pOrigin;
pPoint3 = LoadPoint( *parser ) + pOrigin;
pPoint4 = LoadPoint( *parser ) + pOrigin;
auto const minheight { parser->getToken<double>() };
fHeightDifference = ( pPoint3.y - pPoint1.y + pPoint4.y - pPoint2.y ) * 0.5 - minheight;
auto const segmentlength { parser->getToken<double>() };
iNumSections = (
segmentlength ?
glm::length( ( pPoint1 - pPoint2 ) ) / segmentlength :
0 );
parser->getTokens( 2 );
*parser
>> Wires
>> WireOffset;
m_visible = ( parser->getToken<std::string>() == "vis" );
std::string token { parser->getToken<std::string>() };
if( token == "parallel" ) {
// jawne wskazanie innego przęsła, na które może przestawić się pantograf
parser->getTokens();
*parser >> asParallel;
}
while( ( false == token.empty() )
&& ( token != "endtraction" ) ) {
token = parser->getToken<std::string>();
}
Init(); // przeliczenie parametrów
// calculate traction location
location( interpolate( pPoint2, pPoint1, 0.5 ) );
}
// retrieves list of the track's end points
std::vector<glm::dvec3>
TTraction::endpoints() const {
return { pPoint1, pPoint2 };
}
std::size_t
TTraction::create_geometry( gfx::geometrybank_handle const &Bank ) {
if( m_geometry != null_handle ) {
return GfxRenderer->Vertices( m_geometry ).size() / 2;
}
gfx::vertex_array vertices;
double ddp = std::hypot( pPoint2.x - pPoint1.x, pPoint2.z - pPoint1.z );
if( Wires == 2 )
WireOffset = 0;
// jezdny
gfx::basic_vertex startvertex, endvertex;
startvertex.position =
glm::vec3(
pPoint1.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x,
pPoint1.y - m_origin.y,
pPoint1.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z );
endvertex.position =
glm::vec3(
pPoint2.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x,
pPoint2.y - m_origin.y,
pPoint2.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
// Nie wiem co 'Marcin
glm::dvec3 pt1, pt2, pt3, pt4, v1, v2;
v1 = pPoint4 - pPoint3;
v2 = pPoint2 - pPoint1;
float step = 0;
if( iNumSections > 0 )
step = 1.0f / (float)iNumSections;
double f = step;
float mid = 0.5;
float t;
// Przewod nosny 'Marcin
if( Wires > 1 ) { // lina nośna w kawałkach
startvertex.position =
glm::vec3(
pPoint3.x - m_origin.x,
pPoint3.y - m_origin.y,
pPoint3.z - m_origin.z );
for( int i = 0; i < iNumSections - 1; ++i ) {
pt3 = pPoint3 + v1 * f;
t = ( 1 - std::fabs( f - mid ) * 2 );
if( ( Wires < 4 )
|| ( ( i != 0 )
&& ( i != iNumSections - 2 ) ) ) {
endvertex.position =
glm::vec3(
pt3.x - m_origin.x,
pt3.y - std::sqrt( t ) * fHeightDifference - m_origin.y,
pt3.z - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
startvertex = endvertex;
}
f += step;
}
endvertex.position =
glm::vec3(
pPoint4.x - m_origin.x,
pPoint4.y - m_origin.y,
pPoint4.z - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
}
// Drugi przewod jezdny 'Winger
if( Wires > 2 ) {
startvertex.position =
glm::vec3(
pPoint1.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x,
pPoint1.y - m_origin.y,
pPoint1.z + ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z );
endvertex.position =
glm::vec3(
pPoint2.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x,
pPoint2.y - m_origin.y,
pPoint2.z + ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
}
f = step;
if( Wires == 4 ) {
startvertex.position =
glm::vec3(
pPoint3.x - m_origin.x,
pPoint3.y - 0.65f * fHeightDifference - m_origin.y,
pPoint3.z - m_origin.z );
for( int i = 0; i < iNumSections - 1; ++i ) {
pt3 = pPoint3 + v1 * f;
t = ( 1 - std::fabs( f - mid ) * 2 );
endvertex.position =
glm::vec3(
pt3.x - m_origin.x,
pt3.y - std::sqrt( t ) * fHeightDifference - (
( ( i == 0 )
|| ( i == iNumSections - 2 ) ) ?
0.25f * fHeightDifference :
0.05 )
- m_origin.y,
pt3.z - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
startvertex = endvertex;
f += step;
}
endvertex.position =
glm::vec3(
pPoint4.x - m_origin.x,
pPoint4.y - 0.65f * fHeightDifference - m_origin.y,
pPoint4.z - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
}
f = step;
// Przewody pionowe (wieszaki) 'Marcin, poprawki na 2 przewody jezdne 'Winger
if( Wires > 1 ) {
auto const flo { static_cast<float>( Wires == 4 ? 0.25f * fHeightDifference : 0 ) };
auto const flo1 { static_cast<float>( Wires == 4 ? +0.05 : 0 ) };
for( int i = 0; i < iNumSections - 1; ++i ) {
pt3 = pPoint3 + v1 * f;
pt4 = pPoint1 + v2 * f;
t = ( 1 - std::fabs( f - mid ) * 2 );
if( ( i % 2 ) == 0 ) {
startvertex.position =
glm::vec3(
pt3.x - m_origin.x,
pt3.y - std::sqrt( t ) * fHeightDifference - ( ( i == 0 ) || ( i == iNumSections - 2 ) ? flo : flo1 ) - m_origin.y,
pt3.z - m_origin.z );
endvertex.position =
glm::vec3(
pt4.x - ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x,
pt4.y - m_origin.y,
pt4.z - ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
}
else {
startvertex.position =
glm::vec3(
pt3.x - m_origin.x,
pt3.y - std::sqrt( t ) * fHeightDifference - ( ( i == 0 ) || ( i == iNumSections - 2 ) ? flo : flo1 ) - m_origin.y,
pt3.z - m_origin.z );
endvertex.position =
glm::vec3(
pt4.x + ( pPoint2.z / ddp - pPoint1.z / ddp ) * WireOffset - m_origin.x,
pt4.y - m_origin.y,
pt4.z + ( -pPoint2.x / ddp + pPoint1.x / ddp ) * WireOffset - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
}
if( ( ( Wires == 4 )
&& ( ( i == 1 )
|| ( i == iNumSections - 3 ) ) ) ) {
startvertex.position =
glm::vec3(
pt3.x - m_origin.x,
pt3.y - std::sqrt( t ) * fHeightDifference - 0.05 - m_origin.y,
pt3.z - m_origin.z );
endvertex.position =
glm::vec3(
pt3.x - m_origin.x,
pt3.y - std::sqrt( t ) * fHeightDifference - m_origin.y,
pt3.z - m_origin.z );
vertices.emplace_back( startvertex );
vertices.emplace_back( endvertex );
}
f += step;
}
}
auto const elementcount = vertices.size() / 2;
gfx::userdata_array empty_userdata{};
m_geometry = GfxRenderer->Insert( vertices, empty_userdata, Bank, GL_LINES );
return elementcount;
}
int TTraction::TestPoint(glm::dvec3 const &Point)
{ // sprawdzanie, czy przęsła można połączyć
if( ( hvNext[ 0 ] == nullptr )
&& ( glm::all( glm::epsilonEqual( Point, pPoint1, 0.025 ) ) ) ) {
return 0;
}
if( ( hvNext[ 1 ] == nullptr )
&& ( glm::all( glm::epsilonEqual( Point, pPoint2, 0.025 ) ) ) ) {
return 1;
}
return -1;
};
//łączenie segmentu (with) od strony (my) do jego (to)
void
TTraction::Connect(int my, TTraction *with, int to) {
hvNext[ my ] = with;
iNext[ my ] = to;
if( ( hvNext[ 0 ] != nullptr )
&& ( hvNext[ 1 ] != nullptr ) ) {
// jeśli z obu stron podłączony to nie jest ostatnim
iLast = 0;
}
if( with == nullptr ) { return; }
with->hvNext[ to ] = this;
with->iNext[ to ] = my;
if( ( with->hvNext[ 0 ] != nullptr )
&& ( with->hvNext[ 1 ] != nullptr ) ) {
// temu też, bo drugi raz łączenie się nie nie wykona
with->iLast = 0;
}
}
// ustalenie przedostatnich przęseł
bool TTraction::WhereIs() {
if( iLast ) {
// ma już ustaloną informację o położeniu
return ( iLast & 1 );
}
for( int endindex = 0; endindex < 2; ++endindex ) {
if( hvNext[ endindex ] == nullptr ) {
// no neighbour piece means this one is last
iLast |= 1;
}
else if( hvNext[ endindex ]->hvNext[ 1 - iNext[ endindex ] ] == nullptr ) {
// otherwise if that neighbour has no further connection on the opposite end then this piece is second-last
iLast |= 2;
}
}
return (iLast & 1); // ostatnie będą dostawać zasilanie
}
void TTraction::Init()
{ // przeliczenie parametrów
vParametric = pPoint2 - pPoint1; // wektor mnożników parametru dla równania parametrycznego
};
void TTraction::ResistanceCalc(int d, double r, TTractionPowerSource *ps)
{ //(this) jest przęsłem zasilanym, o rezystancji (r), policzyć rezystancję zastępczą sąsiednich
if (d >= 0)
{ // podążanie we wskazanym kierunku
TTraction *t = hvNext[d], *p;
if (ps)
psPower[d ^ 1] = ps; // podłączenie podanego
else
ps = psPower[d ^ 1]; // zasilacz od przeciwnej strony niż idzie analiza
d = iNext[d]; // kierunek
PowerState = 4;
while( ( t != nullptr )
&& ( t->psPower[d] == nullptr ) ) // jeśli jest jakiś kolejny i nie ma ustalonego zasilacza
{ // ustawienie zasilacza i policzenie rezystancji zastępczej
if( t->PowerState != 4 ) {
// przęsła zasilającego nie modyfikować
if( t->psPowered != nullptr ) {
t->PowerState = 4;
}
else {
// kolor zależny od strony, z której jest zasilanie
t->PowerState |= d ? 2 : 1;
}
}
t->psPower[d] = ps; // skopiowanie wskaźnika zasilacza od danej strony
t->fResistance[d] = r; // wpisanie rezystancji w kierunku tego zasilacza
r += t->fResistivity * glm::length(t->vParametric); // doliczenie oporu kolejnego odcinka
p = t; // zapamiętanie dotychczasowego
t = p->hvNext[d ^ 1]; // podążanie w tę samą stronę
d = p->iNext[d ^ 1];
// w przypadku zapętlenia sieci może się zawiesić?
}
}
else
{ // podążanie w obu kierunkach, można by rekurencją, ale szkoda zasobów
r = 0.5 * fResistivity * glm::length(vParametric); // powiedzmy, że w zasilanym przęśle jest połowa
if (fResistance[0] == 0.0)
ResistanceCalc(0, r); // do tyłu (w stronę Point1)
if (fResistance[1] == 0.0)
ResistanceCalc(1, r); // do przodu (w stronę Point2)
}
};
void TTraction::PowerSet(TTractionPowerSource *ps)
{ // podłączenie przęsła do zasilacza
if (ps->bSection)
psSection = ps; // ustalenie sekcji zasilania
else
{ // ustalenie punktu zasilania (nie ma jeszcze połączeń między przęsłami)
psPowered = ps; // ustawienie bezpośredniego zasilania dla przęsła
psPower[0] = psPower[1] = ps; // a to chyba nie jest dobry pomysł, bo nawet zasilane przęsło
// powinno mieć wskazania na inne
fResistance[0] = fResistance[1] = 0.0; // a liczy się tylko rezystancja zasilacza
}
};
double TTraction::VoltageGet(double u, double i)
{ // pobranie napięcia na przęśle po podłączeniu do niego rezystancji (res) - na razie jest to prąd
if (!psSection)
if (!psPowered)
return NominalVoltage; // jak nie ma zasilacza, to napięcie podane w przęśle
// na początek można założyć, że wszystkie podstacje mają to samo napięcie i nie płynie prąd
// pomiędzy nimi
// dla danego przęsła mamy 3 źródła zasilania
// 1. zasilacz psPower[0] z rezystancją fResistance[0] oraz jego wewnętrzną
// 2. zasilacz psPower[1] z rezystancją fResistance[1] oraz jego wewnętrzną
// 3. zasilacz psPowered z jego wewnętrzną rezystancją dla przęseł zasilanych bezpośrednio
double res = (
(i != 0.0) ?
(u / i) :
10000.0 );
if( psPowered != nullptr ) {
// yB: dla zasilanego nie baw się w gwiazdy, tylko bierz bezpośrednio
return (
( res != 0.0 ) ?
psPowered->CurrentGet( res ) * res :
0.0 );
}
if( ( psPower[0] && psPower[0]->Fuse() )
|| ( psPower[1] && psPower[1]->Fuse() ) ) {
// if either power source is out, so are we
return 0.0;
}
double r0t, r1t, r0g, r1g;
double i0, i1;
r0t = fResistance[0]; //średni pomysł, ale lepsze niż nic
r1t = fResistance[1]; // bo nie uwzględnia spadków z innych pojazdów
if (psPower[0] && psPower[1])
{ // gdy przęsło jest zasilane z obu stron - mamy trójkąt: res, r0t, r1t
// yB: Gdy wywali podstacja, to zaczyna się robić nieciekawie - napięcie w sekcji na jednym końcu jest równe zasilaniu,
// yB: a na drugim końcu jest równe 0. Kolejna sprawa to rozróżnienie uszynienia sieci na podstacji/odłączniku (czyli
// yB: potencjał masy na sieci) od braku zasilania (czyli odłączenie źródła od sieci i brak jego wpływu na napięcie).
if ((r0t > 0.0) && (r1t > 0.0))
{ // rezystancje w mianowniku nie mogą być zerowe
r0g = res + r0t + (res * r0t) / r1t; // przeliczenie z trójkąta na gwiazdę
r1g = res + r1t + (res * r1t) / r0t;
// pobierane są prądy dla każdej rezystancji, a suma jest mnożona przez rezystancję
// pojazdu w celu uzyskania napięcia
i0 = psPower[0]->CurrentGet(r0g); // oddzielnie dla sprawdzenia
i1 = psPower[1]->CurrentGet(r1g);
return (i0 + i1) * res;
}
else if (r0t >= 0.0)
return psPower[0]->CurrentGet(res + r0t) * res;
else if (r1t >= 0.0)
return psPower[1]->CurrentGet(res + r1t) * res;
else
return 0.0; // co z tym zrobić?
}
else if (psPower[0] && (r0t >= 0.0))
{ // jeśli odcinek podłączony jest tylko z jednej strony
return psPower[0]->CurrentGet(res + r0t) * res;
}
else if (psPower[1] && (r1t >= 0.0))
return psPower[1]->CurrentGet(res + r1t) * res;
return 0.0; // gdy nie podłączony wcale?
};
glm::vec3
TTraction::wire_color() const {
glm::vec3 color;
if( !DebugTractionFlag )
{
switch( Material ) { // Ra: kolory podzieliłem przez 2, bo po zmianie ambient za jasne były
// trzeba uwzględnić kierunek świecenia Słońca - tylko ze Słońcem widać kolor
case 1: {
if( TestFlag( DamageFlag, 1 ) ) {
color.r = 0.00000f;
color.g = 0.32549f;
color.b = 0.2882353f; // zielona miedź
}
else {
color.r = 0.35098f;
color.g = 0.22549f;
color.b = 0.1f; // czerwona miedź
}
break;
}
case 2: {
if( TestFlag( DamageFlag, 1 ) ) {
color.r = 0.10f;
color.g = 0.10f;
color.b = 0.10f; // czarne Al
}
else {
color.r = 0.25f;
color.g = 0.25f;
color.b = 0.25f; // srebrne Al
}
break;
}
default: {break; }
}
color *= 0.2;
// w zaleźności od koloru swiatła
color.r *= Global.DayLight.ambient[ 0 ];
color.g *= Global.DayLight.ambient[ 1 ];
color.b *= Global.DayLight.ambient[ 2 ];
color *= 0.35f;
}
else {
// tymczasowo pokazanie zasilanych odcinków
switch( PowerState ) {
case 1: {
// cyan
color = glm::vec3 { 0.f, 174.f / 255.f, 239.f / 255.f };
break;
}
case 2: {
// yellow
color = glm::vec3 { 240.f / 255.f, 228.f / 255.f, 0.f };
break;
}
case 3: {
// green
color = glm::vec3 { 0.f, 239.f / 255.f, 118.f / 255.f };
break;
}
case 4: {
// white for powered, red for ends
color = (
psPowered != nullptr ?
glm::vec3{ 239.f / 255.f, 239.f / 255.f, 239.f / 255.f } :
glm::vec3{ 239.f / 255.f, 128.f / 255.f, 128.f / 255.f } );
break;
}
default: { break; }
}
if( hvParallel ) { // jeśli z bieżnią wspólną, to dodatkowo przyciemniamy
color.r *= 0.6f;
color.g *= 0.6f;
color.b *= 0.6f;
}
}
return color;
}
// radius() subclass details, calculates node's bounding radius
float
TTraction::radius_() {
auto const points { endpoints() };
auto radius { 0.f };
for( auto &point : points ) {
radius = std::max(
radius,
static_cast<float>( glm::length( m_area.center - point ) ) );
}
return radius;
}
// serialize() subclass details, sends content of the subclass to provided stream
void
TTraction::serialize_( std::ostream &Output ) const {
// TODO: implement
}
// deserialize() subclass details, restores content of the subclass from provided stream
void
TTraction::deserialize_( std::istream &Input ) {
// TODO: implement
}
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void
TTraction::export_as_text_( std::ostream &Output ) const {
// header
Output << "traction ";
// basic attributes
Output
<< asPowerSupplyName << ' '
<< NominalVoltage << ' '
<< MaxCurrent << ' '
<< ( fResistivity * 1000 ) << ' '
<< (
Material == 2 ? "al" :
Material == 0 ? "none" :
"cu" ) << ' '
<< WireThickness << ' '
<< DamageFlag << ' ';
// path data
Output
<< pPoint1.x << ' ' << pPoint1.y << ' ' << pPoint1.z << ' '
<< pPoint2.x << ' ' << pPoint2.y << ' ' << pPoint2.z << ' '
<< pPoint3.x << ' ' << pPoint3.y << ' ' << pPoint3.z << ' '
<< pPoint4.x << ' ' << pPoint4.y << ' ' << pPoint4.z << ' ';
// minimum height
Output << ( ( pPoint3.y - pPoint1.y + pPoint4.y - pPoint2.y ) * 0.5 - fHeightDifference ) << ' ';
// segment length
Output << static_cast<int>( iNumSections ? glm::length( pPoint1 - pPoint2 ) / iNumSections : 0.0 ) << ' ';
// wire data
Output
<< Wires << ' '
<< WireOffset << ' ';
// visibility
// NOTE: 'invis' would be less wrong than 'unvis', but potentially incompatible with old 3rd party tools
Output << ( m_visible ? "vis" : "unvis" ) << ' ';
// optional attributes
if( false == asParallel.empty() ) {
Output << "parallel " << asParallel << ' ';
}
// footer
Output
<< "endtraction"
<< "\n";
}
// legacy method, initializes traction after deserialization from scenario file
void
traction_table::InitTraction() {
//łączenie drutów ze sobą oraz z torami i eventami
// TGroundNode *nCurrent, *nTemp;
// TTraction *tmp; // znalezione przęsło
int connection { -1 };
TTraction *matchingtraction { nullptr };
for( auto *traction : m_items ) {
// podłączenie do zasilacza, żeby można było sumować prąd kilku pojazdów
// a jednocześnie z jednego miejsca zmieniać napięcie eventem
// wykonywane najpierw, żeby można było logować podłączenie 2 zasilaczy do jednego drutu
// izolator zawieszony na przęśle jest ma być osobnym odcinkiem drutu o długości ok. 1m,
// podłączonym do zasilacza o nazwie "*" (gwiazka); "none" nie będzie odpowiednie
auto *powersource = simulation::Powergrid.find( traction->asPowerSupplyName );
if( powersource ) {
// jak zasilacz znaleziony to podłączyć do przęsła
traction->PowerSet( powersource );
}
else {
if( ( traction->asPowerSupplyName != "*" ) // gwiazdka dla przęsła z izolatorem
&& ( traction->asPowerSupplyName != "none" ) ) { // dopuszczamy na razie brak podłączenia?
// logowanie błędu i utworzenie zasilacza o domyślnej zawartości
ErrorLog( "Bad scenario: traction piece connected to nonexistent power source \"" + traction->asPowerSupplyName + "\"" );
scene::node_data nodedata;
nodedata.name = traction->asPowerSupplyName;
powersource = new TTractionPowerSource( nodedata );
powersource->IsAutogenerated = true;
powersource->Init( traction->NominalVoltage, traction->MaxCurrent );
simulation::Powergrid.insert( powersource );
}
}
}
#ifdef EU07_IGNORE_LEGACYPROCESSINGORDER
for( auto *traction : m_items ) {
#else
// NOTE: legacy code peformed item operations last-to-first due to way the items were added to the list
// this had impact in situations like two possible connection candidates, where only the first one would be used
for( auto first = std::rbegin(m_items); first != std::rend(m_items); ++first ) {
auto *traction = *first;
#endif
if( traction->hvNext[ 0 ] == nullptr ) {
// tylko jeśli jeszcze nie podłączony
std::tie( matchingtraction, connection ) = simulation::Region->find_traction( traction->pPoint1, traction );
switch (connection) {
case 0:
case 1: {
traction->Connect( 0, matchingtraction, connection );
break;
}
default: {
break;
}
}
if( traction->hvNext[ 0 ] ) {
// jeśli został podłączony
if( ( traction->psSection != nullptr )
&& ( matchingtraction->psSection != nullptr ) ) {
// tylko przęsło z izolatorem może nie mieć zasilania, bo ma 2, trzeba sprawdzać sąsiednie
if( traction->psSection != matchingtraction->psSection ) {
// połączone odcinki mają różne zasilacze
// to może być albo podłączenie podstacji lub kabiny sekcyjnej do sekcji, albo błąd
if( ( true == traction->psSection->bSection )
&& ( false == matchingtraction->psSection->bSection ) ) {
//(tmp->psSection) jest podstacją, a (Traction->psSection) nazwą sekcji
matchingtraction->PowerSet( traction->psSection ); // zastąpienie wskazaniem sekcji
}
else if( ( false == traction->psSection->bSection )
&& ( true == matchingtraction->psSection->bSection ) ) {
//(Traction->psSection) jest podstacją, a (tmp->psSection) nazwą sekcji
traction->PowerSet( matchingtraction->psSection ); // zastąpienie wskazaniem sekcji
}
else {
// jeśli obie to sekcje albo obie podstacje, to będzie błąd
ErrorLog( "Bad scenario: faulty traction power connection at location " + to_string( traction->pPoint1 ) );
}
}
}
}
}
if( traction->hvNext[ 1 ] == nullptr ) {
// tylko jeśli jeszcze nie podłączony
std::tie( matchingtraction, connection ) = simulation::Region->find_traction( traction->pPoint2, traction );
switch (connection) {
case 0:
case 1: {
traction->Connect( 1, matchingtraction, connection );
break;
}
default: {
break;
}
}
if( traction->hvNext[ 1 ] ) {
// jeśli został podłączony
if( ( traction->psSection != nullptr )
&& ( matchingtraction->psSection != nullptr ) ) {
// tylko przęsło z izolatorem może nie mieć zasilania, bo ma 2, trzeba sprawdzać sąsiednie
if( traction->psSection != matchingtraction->psSection ) {
// to może być albo podłączenie podstacji lub kabiny sekcyjnej do sekcji, albo błąd
if( ( true == traction->psSection->bSection )
&& ( false == matchingtraction->psSection->bSection ) ) {
//(tmp->psSection) jest podstacją, a (Traction->psSection) nazwą sekcji
matchingtraction->PowerSet( traction->psSection ); // zastąpienie wskazaniem sekcji
}
else if( ( false == traction->psSection->bSection )
&& ( true == matchingtraction->psSection->bSection ) ) {
//(Traction->psSection) jest podstacją, a (tmp->psSection) nazwą sekcji
traction->PowerSet( matchingtraction->psSection ); // zastąpienie wskazaniem sekcji
}
else {
// jeśli obie to sekcje albo obie podstacje, to będzie błąd
ErrorLog( "Bad scenario: faulty traction power connection at location " + to_string( traction->pPoint2 ) );
}
}
}
}
}
}
auto endcount { 0 };
for( auto *traction : m_items ) {
// operacje mające na celu wykrywanie bieżni wspólnych i łączenie przęseł naprążania
if( traction->WhereIs() ) {
// true for outer pieces of the traction section
traction->iTries = 5; // oznaczanie końcowych
++endcount;
}
if (traction->fResistance[0] == 0.0) {
// obliczanie przęseł w segmencie z bezpośrednim zasilaniem
traction->ResistanceCalc();
traction->iTries = 0; // nie potrzeba mu szukać zasilania
}
}
std::vector<TTraction *> ends; ends.reserve( endcount );
for( auto *traction : m_items ) {
//łączenie bieżni wspólnych, w tym oznaczanie niepodanych jawnie
if( false == traction->asParallel.empty() ) {
// będzie wskaźnik na inne przęsło
if( ( traction->asParallel == "none" )
|| ( traction->asParallel == "*" ) ) {
// jeśli nieokreślone
traction->iLast |= 2; // jakby przedostatni - niech po prostu szuka (iLast już przeliczone)
}
else if( traction->hvParallel == nullptr ) {
// jeśli jeszcze nie został włączony w kółko
auto *nTemp = find( traction->asParallel );
if( nTemp != nullptr ) {
// o ile zostanie znalezione przęsło o takiej nazwie
if( nTemp->hvParallel == nullptr ) {
// jeśli tamten jeszcze nie ma wskaźnika bieżni wspólnej
traction->hvParallel = nTemp; // wpisać siebie i dalej dać mu wskaźnik zwrotny
}
else {
// a jak ma, to albo dołączyć się do kółeczka
traction->hvParallel = nTemp->hvParallel; // przjąć dotychczasowy wskaźnik od niego
}
nTemp->hvParallel = traction; // i na koniec ustawienie wskaźnika zwrotnego
}
if( traction->hvParallel == nullptr ) {
ErrorLog( "Missed overhead: " + traction->asParallel ); // logowanie braku
}
}
}
if( traction->iTries == 5 ) {
// jeśli zaznaczony do podłączenia
// wypełnianie tabeli końców w celu szukania im połączeń
ends.emplace_back( traction );
}
}
bool connected; // nieefektywny przebieg kończy łączenie
do {
// ustalenie zastępczej rezystancji dla każdego przęsła
// flaga podłączonych przęseł końcowych: -1=puste wskaźniki, 0=coś zostało, 1=wykonano łączenie
connected = false;
for( auto &end : ends ) {
// załatwione będziemy zerować
if( end == nullptr ) { continue; }
// każdy przebieg to próba podłączenia końca segmentu naprężania do innego zasilanego przęsła
if( end->hvNext[ 0 ] != nullptr ) {
// jeśli końcowy ma ciąg dalszy od strony 0 (Point1), szukamy odcinka najbliższego do Point2
std::tie( matchingtraction, connection ) = simulation::Region->find_traction( end->pPoint2, end, 0 );
if( matchingtraction != nullptr ) {
// jak znalezione przęsło z zasilaniem, to podłączenie "równoległe"
end->ResistanceCalc( 0, matchingtraction->fResistance[ connection ], matchingtraction->psPower[ connection ] );
// jak coś zostało podłączone, to może zasilanie gdzieś dodatkowo dotrze
connected = true;
end = nullptr;
}
}
else if( end->hvNext[ 1 ] != nullptr ) {
// jeśli końcowy ma ciąg dalszy od strony 1 (Point2), szukamy odcinka najbliższego do Point1
std::tie( matchingtraction, connection ) = simulation::Region->find_traction( end->pPoint1, end, 1 );
if( matchingtraction != nullptr ) {
// jak znalezione przęsło z zasilaniem, to podłączenie "równoległe"
end->ResistanceCalc( 1, matchingtraction->fResistance[ connection ], matchingtraction->psPower[ connection ] );
// jak coś zostało podłączone, to może zasilanie gdzieś dodatkowo dotrze
connected = true;
end = nullptr;
}
}
else {
// gdy koniec jest samotny, to na razie nie zostanie podłączony (nie powinno takich być)
end = nullptr;
}
}
} while( true == connected );
}

102
world/Traction.h Normal file
View File

@@ -0,0 +1,102 @@
/*
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/.
*/
#pragma once
#include <string>
#include "scenenode.h"
#include "Segment.h"
#include "material.h"
#include "Names.h"
class TTractionPowerSource;
class TTraction : public scene::basic_node
{
friend opengl_renderer;
friend opengl33_renderer;
public: // na razie
TTractionPowerSource *psPower[ 2 ] { nullptr, nullptr }; // najbliższe zasilacze z obu kierunków
TTractionPowerSource *psPowered { nullptr }; // ustawione tylko dla bezpośrednio zasilanego przęsła
TTraction *hvNext[ 2 ] { nullptr, nullptr }; //łączenie drutów w sieć
int iNext[ 2 ] { 0, 0 }; // do którego końca się łączy
int iLast { 0 }; //że niby ostatni drut // ustawiony bit 0, jeśli jest ostatnim drutem w sekcji; bit1 - przedostatni
public:
glm::dvec3 pPoint1, pPoint2, pPoint3, pPoint4;
glm::dvec3 vParametric; // współczynniki równania parametrycznego odcinka
double fHeightDifference { 0.0 };
int iNumSections { 0 };
float NominalVoltage { 0.0f };
float MaxCurrent { 0.0f };
float fResistivity { 0.0f }; //[om/m], przeliczone z [om/km]
unsigned int Material { 0 }; // 1: Cu, 2: Al
float WireThickness { 0.0f };
unsigned int DamageFlag { 0 }; // 1: zasniedziale, 128: zerwana
int Wires { 2 };
float WireOffset { 0.0f };
std::string asPowerSupplyName; // McZapkie: nazwa podstacji trakcyjnej
TTractionPowerSource *psSection { nullptr }; // zasilacz (opcjonalnie może to być pulpit sterujący EL2 w hali!)
std::string asParallel; // nazwa przęsła, z którym może być bieżnia wspólna
TTraction *hvParallel { nullptr }; // jednokierunkowa i zapętlona lista przęseł ewentualnej bieżni wspólnej
float fResistance[ 2 ] { -1.0f, -1.0f }; // rezystancja zastępcza do punktu zasilania (0: przęsło zasilane, <0: do policzenia)
int iTries { 1 }; // 0 is used later down the road to mark directly powered pieces
int PowerState { 0 }; // type of incoming power, if any
// visualization data
glm::dvec3 m_origin;
gfx::geometry_handle m_geometry;
explicit TTraction( scene::node_data const &Nodedata );
void Load( cParser *parser, glm::dvec3 const &pOrigin );
// set origin point
void
origin( glm::dvec3 Origin ) {
m_origin = Origin; }
// retrieves list of the track's end points
std::vector<glm::dvec3>
endpoints() const;
// creates geometry data in specified geometry bank. returns: number of created elements, or NULL
// NOTE: deleting nodes doesn't currently release geometry data owned by the node. TODO: implement erasing individual geometry chunks and banks
std::size_t create_geometry( gfx::geometrybank_handle const &Bank );
int TestPoint(glm::dvec3 const &Point);
void Connect(int my, TTraction *with, int to);
void Init();
bool WhereIs();
void ResistanceCalc(int d = -1, double r = 0, TTractionPowerSource *ps = nullptr);
void PowerSet(TTractionPowerSource *ps);
double VoltageGet(double u, double i);
private:
// methods
glm::vec3 wire_color() const;
// radius() subclass details, calculates node's bounding radius
float radius_();
// serialize() subclass details, sends content of the subclass to provided stream
void serialize_( std::ostream &Output ) const;
// deserialize() subclass details, restores content of the subclass from provided stream
void deserialize_( std::istream &Input );
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void export_as_text_( std::ostream &Output ) const;
};
// collection of virtual tracks and roads present in the scene
class traction_table : public basic_table<TTraction> {
public:
// legacy method, initializes traction after deserialization from scenario file
void
InitTraction();
};
//---------------------------------------------------------------------------

203
world/TractionPower.cpp Normal file
View File

@@ -0,0 +1,203 @@
/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator component
Copyright (C) 2004 Maciej Czapkiewicz and others
*/
#include "stdafx.h"
#include "TractionPower.h"
#include "parser.h"
#include "Logs.h"
//---------------------------------------------------------------------------
TTractionPowerSource::TTractionPowerSource( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {}
// legacy constructor
void TTractionPowerSource::Init(double const u, double const i)
{ // ustawianie zasilacza przy braku w scenerii
NominalVoltage = u;
VoltageFrequency = 0;
MaxOutputCurrent = i;
};
bool TTractionPowerSource::Load(cParser *parser) {
parser->getTokens( 10, false );
*parser
>> m_area.center.x
>> m_area.center.y
>> m_area.center.z
>> NominalVoltage
>> VoltageFrequency
>> InternalRes
>> MaxOutputCurrent
>> FastFuseTimeOut
>> FastFuseRepetition
>> SlowFuseTimeOut;
std::string token { parser->getToken<std::string>() };
if( token == "recuperation" ) {
Recuperation = true;
}
else if( token == "section" ) {
// odłącznik sekcyjny
// nie jest źródłem zasilania, a jedynie informuje o prądzie odłączenia sekcji z obwodu
bSection = true;
}
// skip rest of the section
while( ( false == token.empty() )
&& ( token != "end" ) ) {
token = parser->getToken<std::string>();
}
if( InternalRes < 0.1 ) {
// coś mała ta rezystancja była...
// tak około 0.2, wg
// http://www.ikolej.pl/fileadmin/user_upload/Seminaria_IK/13_05_07_Prezentacja_Kruczek.pdf
InternalRes = 0.2;
}
return true;
};
bool TTractionPowerSource::Update(double dt)
{ // powinno być wykonane raz na krok fizyki
// iloczyn napięcia i admitancji daje prąd
if( FastFuse || SlowFuse ) {
TotalCurrent = 0.0;
}
if (TotalCurrent > MaxOutputCurrent) {
FastFuse = true;
FuseCounter += 1;
if (FuseCounter > FastFuseRepetition) {
SlowFuse = true;
ErrorLog( "Power overload: \"" + m_name + "\" disabled for " + std::to_string( SlowFuseTimeOut ) + "s" );
}
else {
ErrorLog( "Power overload: \"" + m_name + "\" disabled for " + std::to_string( FastFuseTimeOut ) + "s" );
}
FuseTimer = 0;
}
if (FastFuse || SlowFuse)
{ // jeśli któryś z bezpieczników zadziałał
TotalAdmitance = 0;
FuseTimer += dt;
if (!SlowFuse)
{ // gdy szybki, odczekać krótko i załączyć
if (FuseTimer > FastFuseTimeOut)
FastFuse = false;
}
else if (FuseTimer > SlowFuseTimeOut)
{
SlowFuse = false;
FuseCounter = 0; // dajemy znów szansę
}
}
TotalPreviousAdmitance = TotalAdmitance; // używamy admitancji z poprzedniego kroku
if (TotalPreviousAdmitance == 0.0)
TotalPreviousAdmitance = 1e-10; // przynajmniej minimalna upływność
TotalAdmitance = 1e-10; // a w aktualnym kroku sumujemy admitancję
return true;
};
double TTractionPowerSource::CurrentGet(double res)
{ // pobranie wartości prądu przypadającego na rezystancję (res)
// niech pamięta poprzednią admitancję i wg niej przydziela prąd
if (SlowFuse || FastFuse)
{ // czekanie na zanik obciążenia sekcji
if (res < 100.0) // liczenie czasu dopiero, gdy obciążenie zniknie
FuseTimer = 0;
return 0;
}
if ((res > 0) || ((res < 0) && (Recuperation || true)))
TotalAdmitance += 1.0 / res; // połączenie równoległe rezystancji jest równoważne sumie admitancji
float NomVolt = (TotalPreviousAdmitance < 0 ? NominalVoltage * 1.083 : NominalVoltage);
TotalCurrent = (TotalPreviousAdmitance != 0.0) ?
NomVolt / (InternalRes + 1.0 / TotalPreviousAdmitance) :
0.0; // napięcie dzielone przez sumę rezystancji wewnętrznej i obciążenia
OutputVoltage = NomVolt - InternalRes * TotalCurrent; // napięcie na obciążeniu
return TotalCurrent / (res * TotalPreviousAdmitance); // prąd proporcjonalny do udziału (1/res)
// w całkowitej admitancji
};
void TTractionPowerSource::PowerSet(TTractionPowerSource *ps)
{ // wskazanie zasilacza w obiekcie sekcji
if (!psNode[0])
psNode[0] = ps;
else if (!psNode[1])
psNode[1] = ps;
// else ErrorLog("nie może być więcej punktów zasilania niż dwa");
};
// serialize() subclass details, sends content of the subclass to provided stream
void
TTractionPowerSource::serialize_( std::ostream &Output ) const {
// TODO: implement
}
// deserialize() subclass details, restores content of the subclass from provided stream
void
TTractionPowerSource::deserialize_( std::istream &Input ) {
// TODO: implement
}
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void
TTractionPowerSource::export_as_text_( std::ostream &Output ) const {
// header
Output << "tractionpowersource ";
// placement
Output
<< location().x << ' '
<< location().y << ' '
<< location().z << ' ';
// basic attributes
Output
<< NominalVoltage << ' '
<< VoltageFrequency << ' '
<< InternalRes << ' '
<< MaxOutputCurrent << ' '
<< FastFuseTimeOut << ' '
<< FastFuseRepetition << ' '
<< SlowFuseTimeOut << ' ';
// optional attributes
if( true == Recuperation ) {
Output << "recuperation ";
}
if( true == bSection ) {
Output << "section ";
}
// footer
Output
<< "end"
<< "\n";
}
// legacy method, calculates changes in simulation state over specified time
void
powergridsource_table::update( double const Deltatime ) {
for( auto *powersource : m_items ) {
powersource->Update( Deltatime );
}
}
//---------------------------------------------------------------------------

79
world/TractionPower.h Normal file
View File

@@ -0,0 +1,79 @@
/*
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/.
*/
#pragma once
#include "Classes.h"
#include "scenenode.h"
#include "Names.h"
class TTractionPowerSource : public scene::basic_node {
friend class debug_panel;
public:
// constructor
TTractionPowerSource( scene::node_data const &Nodedata );
// methods
void Init(double const u, double const i);
bool Load(cParser *parser);
bool Update(double dt);
double CurrentGet(double res);
void VoltageSet(double const v) {
NominalVoltage = v; };
void PowerSet(TTractionPowerSource *ps);
bool Fuse() const {
return ( FastFuse || SlowFuse ); }
// members
TTractionPowerSource *psNode[ 2 ] = { nullptr, nullptr }; // zasilanie na końcach dla sekcji
bool bSection = false; // czy jest sekcją
bool IsAutogenerated { false }; // indicates autogenerated source for individual traction piece
private:
// methods
// serialize() subclass details, sends content of the subclass to provided stream
void serialize_( std::ostream &Output ) const;
// deserialize() subclass details, restores content of the subclass from provided stream
void deserialize_( std::istream &Input );
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void export_as_text_( std::ostream &Output ) const;
// members
double NominalVoltage = 0.0;
double VoltageFrequency = 0.0;
double InternalRes = 0.2;
double MaxOutputCurrent = 0.0;
double FastFuseTimeOut = 1.0;
int FastFuseRepetition = 3;
double SlowFuseTimeOut = 60.0;
bool Recuperation = false;
double TotalCurrent = 0.0;
double TotalAdmitance = 1e-10; // 10Mom - jakaś tam upływność
double TotalPreviousAdmitance = 1e-10; // zero jest szkodliwe
double OutputVoltage = 0.0;
bool FastFuse = false;
bool SlowFuse = false;
double FuseTimer = 0.0;
int FuseCounter = 0;
};
// collection of generators for power grid present in the scene
class powergridsource_table : public basic_table<TTractionPowerSource> {
public:
// legacy method, calculates changes in simulation state over specified time
void
update( double const Deltatime );
};
//---------------------------------------------------------------------------

285
world/TrkFoll.cpp Normal file
View File

@@ -0,0 +1,285 @@
/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak and others
*/
#include "stdafx.h"
#include "TrkFoll.h"
#include "simulation.h"
#include "Globals.h"
#include "DynObj.h"
#include "Driver.h"
#include "Logs.h"
TTrackFollower::~TTrackFollower()
{
}
bool TTrackFollower::Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir)
{
fDirection = fDir;
Owner = NewOwner;
SetCurrentTrack(pTrack, 0);
iEventFlag = 3; // na torze startowym również wykonać eventy 1/2
iEventallFlag = 3;
if ((pCurrentSegment)) // && (pCurrentSegment->GetLength()<fFirstDistance))
return false;
return true;
}
void TTrackFollower::Reset()
{
fCurrentDistance = 0.0;
fDirection = 1.0;
}
TTrack * TTrackFollower::SetCurrentTrack(TTrack *pTrack, int end)
{ // przejechanie na inny odcinkek toru, z ewentualnym rozpruciem
if (pTrack)
switch (pTrack->eType)
{
case tt_Switch: // jeśli zwrotnica, to przekładamy ją, aby uzyskać dobry segment
{
int i = (end ? pCurrentTrack->iNextDirection : pCurrentTrack->iPrevDirection);
if (i > 0) // jeżeli wjazd z ostrza
pTrack->SwitchForced(i >> 1, Owner); // to przełożenie zwrotnicy - rozprucie!
}
break;
case tt_Cross: // skrzyżowanie trzeba tymczasowo przełączyć, aby wjechać na właściwy tor
{
iSegment = Owner->RouteWish(pTrack); // nr segmentu został ustalony podczas skanowania
// Ra 2014-08: aby ustalić dalszą trasę, należy zapytać AI - trasa jest ustalana podczas
// skanowania
// Ra 15-01: zapytanie AI nie wybiera segmentu - kolejny skanujący może przestawić
// pTrack->CrossSegment(end?pCurrentTrack->iNextDirection:pCurrentTrack->iPrevDirection,i);
// //ustawienie właściwego wskaźnika
// powinno zwracać kierunek do zapamiętania, bo segmenty mogą mieć różny kierunek
// if fDirection=(iSegment>0)?1.0:-1.0; //kierunek na tym segmencie jest ustalany
// bezpośrednio
if (iSegment == 0)
{ // to jest błędna sytuacja - generuje pętle zawracające za skrzyżowaniem - ustalić,
// kiedy powstaje!
iSegment = 1; // doraźna poprawka
}
if ((end ? iSegment : -iSegment) < 0)
fDirection = -fDirection; // wtórna zmiana
pTrack->SwitchForced(abs(iSegment) - 1, NULL); // wybór zapamiętanego segmentu
}
break;
}
if (!pTrack) {
// gdy nie ma toru w kierunku jazdy tworzenie toru wykolejącego na przedłużeniu pCurrentTrack
pTrack = pCurrentTrack->NullCreate(end);
if (!end) // jeśli dodana od strony zero, to zmiana kierunku
fDirection = -fDirection; // wtórna zmiana
}
else
{ // najpierw +1, później -1, aby odcinek izolowany wspólny dla tych torów nie wykrył zera
pTrack->AxleCounter(+1, Owner); // zajęcie nowego toru
if (pCurrentTrack)
pCurrentTrack->AxleCounter(-1, Owner); // opuszczenie tamtego toru
}
pCurrentTrack = pTrack;
pCurrentSegment = ( pCurrentTrack != nullptr ? pCurrentTrack->CurrentSegment() : nullptr );
if (!pCurrentTrack)
Error(Owner->MoverParameters->Name + " at NULL track");
return pCurrentTrack;
};
bool TTrackFollower::Move(double fDistance, bool bPrimary)
{ // przesuwanie wózka po torach o odległość (fDistance), z wyzwoleniem eventów
// bPrimary=true - jest pierwszą osią w pojeździe, czyli generuje eventy i przepisuje pojazd
// Ra: zwraca false, jeśli pojazd ma być usunięty
auto const ismoving { /* ( std::abs( fDistance ) > 0.01 ) && */ ( Owner->GetVelocity() > 0.01 ) };
fDistance *= fDirection; // dystans mnożnony przez kierunek
double s; // roboczy dystans
double dir; // zapamiętany kierunek do sprawdzenia, czy się zmienił
bool bCanSkip; // czy przemieścić pojazd na inny tor
while (true) // pętla wychodzi, gdy przesunięcie wyjdzie zerowe
{ // pętla przesuwająca wózek przez kolejne tory, aż do trafienia w jakiś
if( pCurrentTrack == nullptr ) { return false; } // nie ma toru, to nie ma przesuwania
// TODO: refactor following block as track method
if( pCurrentTrack->m_events ) { // sumaryczna informacja o eventach
// omijamy cały ten blok, gdy tor nie ma on żadnych eventów (większość nie ma)
int const eventfilter { (
false == ismoving ? 0 : // only moving vehicles activate events type 1/2
false == bPrimary ? 0 : // only primary axle activates events type 1/2
Owner->ctOwner == nullptr ?
( fDistance > 0 ? 1 : -1 ) : // loose vehicle has no means to determine 'intended' direction so the filter does effectively nothing in such case
( fDirection > 0 ? 1 : -1 ) * Owner->ctOwner->Direction() * ( Owner->ctOwner->Vehicle()->DirectionGet() == Owner->DirectionGet() ? 1 : -1 ) ) };
if( false == ismoving ) {
//McZapkie-140602: wyzwalanie zdarzenia gdy pojazd stoi
if( ( Owner->Mechanik != nullptr )
&& ( Owner->Mechanik->primary() ) ) {
// tylko dla jednego członu
pCurrentTrack->QueueEvents( pCurrentTrack->m_events0, Owner );
}
pCurrentTrack->QueueEvents( pCurrentTrack->m_events0all, Owner );
}
else if( (fDistance < 0) && ( eventfilter < 0 ) ) {
// event1, eventall1
if( SetFlag( iEventFlag, -1 ) ) {
// zawsze zeruje flagę sprawdzenia, jak mechanik dosiądzie, to się nie wykona
if( ( Owner->Mechanik != nullptr )
&& ( Owner->Mechanik->primary() ) ) {
// tylko dla jednego członu
// McZapkie-280503: wyzwalanie event tylko dla pojazdow z obsada
pCurrentTrack->QueueEvents( pCurrentTrack->m_events1, Owner );
}
}
if( SetFlag( iEventallFlag, -1 ) ) {
// McZapkie-280503: wyzwalanie eventall dla wszystkich pojazdow
pCurrentTrack->QueueEvents( pCurrentTrack->m_events1all, Owner );
}
}
else if( ( fDistance > 0 ) && ( eventfilter > 0 ) ) {
// event2, eventall2
if( SetFlag( iEventFlag, -2 ) ) {
// zawsze ustawia flagę sprawdzenia, jak mechanik dosiądzie, to się nie wykona
if( ( Owner->Mechanik != nullptr )
&& ( Owner->Mechanik->primary() ) ) {
// tylko dla jednego członu
pCurrentTrack->QueueEvents( pCurrentTrack->m_events2, Owner );
}
}
if( SetFlag( iEventallFlag, -2 ) ) {
// sprawdza i zeruje na przyszłość, true jeśli zmieni z 2 na 0
pCurrentTrack->QueueEvents( pCurrentTrack->m_events2all, Owner );
}
}
}
if (!pCurrentSegment) // jeżeli nie ma powiązanego segmentu toru?
return false;
// if (fDistance==0.0) return true; //Ra: jak stoi, to chyba dalej nie ma co kombinować?
s = fCurrentDistance + fDistance; // doliczenie przesunięcia
// Ra: W Point2 toru może znajdować się "dziura", która zamieni energię kinetyczną
// ruchu wzdłużnego na energię potencjalną, zamieniającą się potem na energię
// sprężystości na amortyzatorach. Należałoby we wpisie toru umieścić współczynnik
// podziału energii kinetycznej.
// Współczynnik normalnie 1, z dziurą np. 0.99, a -1 będzie oznaczało 100% odbicia (kozioł).
// Albo w postaci kąta: normalnie 0°, a 180° oznacza 100% odbicia (cosinus powyższego).
/*
if (pCurrentTrack->eType==tt_Cross)
{//zjazdu ze skrzyżowania nie da się określić przez (iPrevDirection) i (iNextDirection)
//int segment=Owner->RouteWish(pCurrentTrack); //numer segmentu dla skrzyżowań
//pCurrentTrack->SwitchForced(abs(segment)-1,NULL); //tymczasowo ustawienie tego segmentu
//pCurrentSegment=pCurrentTrack->CurrentSegment(); //odświeżyć sobie wskaźnik segmentu
(?)
}
*/
if (s < 0)
{ // jeśli przekroczenie toru od strony Point1
bCanSkip = ( bPrimary && pCurrentTrack->CheckDynamicObject( Owner ) );
if( bCanSkip ) {
// tylko główna oś przenosi pojazd do innego toru
// zdejmujemy pojazd z dotychczasowego toru
Owner->MyTrack->RemoveDynamicObject( Owner );
}
dir = fDirection;
if (pCurrentTrack->eType == tt_Cross)
{
if (!SetCurrentTrack(pCurrentTrack->Connected(iSegment, fDirection), 0))
return false; // wyjście z błędem
}
else if (!SetCurrentTrack(pCurrentTrack->Connected(-1, fDirection), 0)) // ustawia fDirection
return false; // wyjście z błędem
if (dir == fDirection) //(pCurrentTrack->iPrevDirection)
{ // gdy kierunek bez zmiany (Point1->Point2)
fCurrentDistance = pCurrentSegment->GetLength();
fDistance = s;
}
else
{ // gdy zmiana kierunku toru (Point1->Point1)
fCurrentDistance = 0;
fDistance = -s;
}
if (bCanSkip)
{ // jak główna oś, to dodanie pojazdu do nowego toru
pCurrentTrack->AddDynamicObject(Owner);
iEventFlag = 3; // McZapkie-020602: umozliwienie uruchamiania event1,2 po zmianie toru
iEventallFlag = 3; // McZapkie-280503: jw, dla eventall1,2
if (!Owner->MyTrack)
return false;
}
continue;
}
else if (s > pCurrentSegment->GetLength())
{ // jeśli przekroczenie toru od strony Point2
bCanSkip = ( bPrimary && pCurrentTrack->CheckDynamicObject( Owner ) );
if (bCanSkip) // tylko główna oś przenosi pojazd do innego toru
Owner->MyTrack->RemoveDynamicObject(Owner); // zdejmujemy pojazd z dotychczasowego toru
fDistance = s - pCurrentSegment->GetLength();
dir = fDirection;
if (pCurrentTrack->eType == tt_Cross)
{
if (!SetCurrentTrack(pCurrentTrack->Connected(iSegment, fDirection), 1))
return false; // wyjście z błędem
}
else if (!SetCurrentTrack(pCurrentTrack->Connected(1, fDirection), 1)) // ustawia fDirection
return false; // wyjście z błędem
if (dir != fDirection) //(pCurrentTrack->iNextDirection)
{ // gdy zmiana kierunku toru (Point2->Point2)
fDistance = -fDistance; //(s-pCurrentSegment->GetLength());
fCurrentDistance = pCurrentSegment->GetLength();
}
else // gdy kierunek bez zmiany (Point2->Point1)
fCurrentDistance = 0;
if (bCanSkip)
{ // jak główna oś, to dodanie pojazdu do nowego toru
pCurrentTrack->AddDynamicObject(Owner);
iEventFlag = 3; // McZapkie-020602: umozliwienie uruchamiania event1,2 po zmianie toru
iEventallFlag = 3;
if (!Owner->MyTrack)
return false;
}
continue;
}
else
{ // gdy zostaje na tym samym torze (przesuwanie już nie zmienia toru)
if (bPrimary)
{ // tylko gdy początkowe ustawienie, dodajemy eventy stania do kolejki
if (Owner->MoverParameters->CabOccupied != 0) {
pCurrentTrack->QueueEvents( pCurrentTrack->m_events1, Owner, -1.0 );
pCurrentTrack->QueueEvents( pCurrentTrack->m_events2, Owner, -1.0 );
}
pCurrentTrack->QueueEvents( pCurrentTrack->m_events1all, Owner, -1.0 );
pCurrentTrack->QueueEvents( pCurrentTrack->m_events2all, Owner, -1.0 );
}
fCurrentDistance = s;
return ComputatePosition(); // przeliczenie XYZ, true o ile nie wyjechał na NULL
}
}
};
bool TTrackFollower::ComputatePosition()
{ // ustalenie współrzędnych XYZ
if (pCurrentSegment) // o ile jest tor
{
pCurrentSegment->RaPositionGet(fCurrentDistance, pPosition, vAngles);
if (fDirection < 0) // kąty zależą jeszcze od zwrotu na torze
{ // kąty są w przedziale <-M_PI;M_PI>
vAngles.x = -vAngles.x; // przechyłka jest w przecinwą stronę
vAngles.y = -vAngles.y; // pochylenie jest w przecinwą stronę
vAngles.z +=
(vAngles.z >= M_PI) ? -M_PI : M_PI; // ale kierunek w planie jest obrócony o 180°
}
if (fOffsetH != 0.0)
{ // jeśli przesunięcie względem osi toru, to je doliczyć
}
return true;
}
return false;
}

63
world/TrkFoll.h Normal file
View File

@@ -0,0 +1,63 @@
/*
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/.
*/
#ifndef TrkFollH
#define TrkFollH
#include "Classes.h"
#include "Segment.h"
// oś poruszająca się po torze
class TTrackFollower {
public:
// constructors
TTrackFollower() = default;
// destructor
~TTrackFollower();
// methods
TTrack * SetCurrentTrack(TTrack *pTrack, int end);
bool Move(double fDistance, bool bPrimary);
inline TTrack * GetTrack() const {
return pCurrentTrack; };
// przechyłka policzona przy ustalaniu pozycji
inline double GetRoll() {
return vAngles.x; };
//{return pCurrentSegment->GetRoll(fCurrentDistance)*fDirection;}; //zamiast liczyć można pobrać
// zwrot na torze
inline double GetDirection() const {
return fDirection; };
// ABu-030403
inline double GetTranslation() const {
return fCurrentDistance; };
// inline double GetLength(vector3 p1, vector3 cp1, vector3 cp2, vector3 p2)
//{ return pCurrentSegment->ComputeLength(p1,cp1,cp2,p2); };
// inline double GetRadius(double L, double d); //McZapkie-150503
bool Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir);
void Reset();
void Render(float fNr);
// members
double fOffsetH = 0.0; // Ra: odległość środka osi od osi toru (dla samochodów) - użyć do wężykowania
Math3D::vector3 pPosition; // współrzędne XYZ w układzie scenerii
Math3D::vector3 vAngles; // x:przechyłka, y:pochylenie, z:kierunek w planie (w radianach)
private:
// methods
bool ComputatePosition(); // przeliczenie pozycji na torze
// members
TTrack *pCurrentTrack = nullptr; // na którym torze siê znajduje
std::shared_ptr<TSegment> pCurrentSegment; // zwrotnice mog¹ mieæ dwa segmenty
double fCurrentDistance = 0.0; // przesuniêcie wzglêdem Point1 w stronê Point2
double fDirection = 1.0; // ustawienie wzglêdem toru: -1.0 albo 1.0, mno¿one przez dystans // jest przodem do Point2
TDynamicObject *Owner = nullptr; // pojazd posiadający
int iEventFlag = 0; // McZapkie-020602: informacja o tym czy wyzwalac zdarzenie: 0,1,2,3
int iEventallFlag = 0;
int iSegment = 0; // który segment toru jest używany (żeby nie przeskakiwało po przestawieniu zwrotnicy pod taborem)
};
//---------------------------------------------------------------------------
#endif

692
world/mtable.cpp Normal file
View File

@@ -0,0 +1,692 @@
/*
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 "mtable.h"
#include "Globals.h"
#include "simulationtime.h"
#include "dictionary.h"
#include "utilities.h"
double TTrainParameters::CheckTrainLatency()
{
if ((LastStationLatency > 1.0) || (LastStationLatency < 0))
return LastStationLatency; /*spoznienie + lub do przodu - z tolerancja 1 min*/
else
return 0;
}
double TTrainParameters::WatchMTable(double DistCounter)
{ // zwraca odleglość do najblizszej stacji z zatrzymaniem
double dist;
if (Direction == 1)
dist = TimeTable[StationIndex].km - TimeTable[0].km - DistCounter;
else
dist = TimeTable[0].km - TimeTable[StationIndex].km - DistCounter;
return dist;
}
std::string TTrainParameters::NextStop() const
{ // pobranie nazwy następnego miejsca zatrzymania
if (StationIndex <= StationCount)
return NextStationName; // nazwa następnego przystanku;
else
return "[End of route]"; //że niby koniec
}
sound_source
TTrainParameters::next_stop_sound() const {
if( StationIndex > StationCount ) {
return { sound_placement::engine };
}
for( auto stationidx { StationIndex }; stationidx < StationCount + 1; ++stationidx ) {
auto &station{ TimeTable[ stationidx ] };
if( station.Ah == -1 ) {
continue;
}
// specified arrival time means it's a scheduled stop
return station.name_sound;
}
// shouldn't normally get here, unless the timetable is malformed
return { sound_placement::engine };
}
sound_source
TTrainParameters::last_stop_sound() const {
return TimeTable[ StationCount ].name_sound;
}
bool TTrainParameters::IsStop() const
{ // zapytanie, czy zatrzymywać na następnym punkcie rozkładu
if ((StationIndex <= StationCount))
return TimeTable[StationIndex].Ah >= 0; //-1 to brak postoju
else
return true; // na ostatnim się zatrzymać zawsze
}
bool TTrainParameters::IsLastStop() const {
return ( StationIndex >= StationCount );
}
bool TTrainParameters::IsMaintenance() const {
if( ( StationIndex <= StationCount ) )
return TimeTable[ StationIndex ].is_maintenance;
else
return false;
}
int TTrainParameters::radio_channel() const {
if( ( StationIndex <= StationCount ) )
return TimeTable[ StationIndex ].radio_channel;
else
return -1;
}
// returns: sound file associated with current station, or -1
sound_source TTrainParameters::current_stop_sound() const {
if( ( StationIndex <= StationCount ) )
return TimeTable[ StationIndex ].name_sound;
else
return { sound_placement::engine };
}
bool TTrainParameters::UpdateMTable( scenario_time const &Time, std::string const &NewName ) {
return UpdateMTable( Time.data().wHour, Time.data().wMinute + Time.data().wSecond * 0.0167, NewName );
}
bool TTrainParameters::UpdateMTable(double hh, double mm, std::string const &NewName)
/*odfajkowanie dojechania do stacji (NewName) i przeliczenie opóźnienia*/
{
bool OK;
OK = false;
if (StationIndex <= StationCount) // Ra: "<=", bo ostatni przystanek jest traktowany wyjątkowo
{
if (NewName == NextStationName) // jeśli dojechane do następnego
{ // Ra: wywołanie może być powtarzane, jak stoi na W4
if (TimeTable[StationIndex + 1].km - TimeTable[StationIndex].km < 0) // to jest bez sensu
Direction = -1;
else
Direction = 1; // prowizorka bo moze byc zmiana kilometrazu
// ustalenie, czy opóźniony (porównanie z czasem odjazdu)
LastStationLatency =
CompareTime(hh, mm, TimeTable[StationIndex].Dh, TimeTable[StationIndex].Dm);
// inc(StationIndex); //przejście do następnej pozycji StationIndex<=StationCount
// Ra: "<", bo dodaje 1 przy przejściu do następnej stacji
if (StationIndex < StationCount) {
// jeśli nie ostatnia stacja
NextStationName = TimeTable[StationIndex + 1].StationName; // zapamiętanie nazwy
// Ra: nowa prędkość rozkładowa na kolejnym odcinku
TTVmax = TimeTable[StationIndex + 1].vmax;
}
else {
// gdy ostatnia stacja, nie ma następnej stacji
NextStationName = "";
}
OK = true;
}
}
return OK; /*czy jest nastepna stacja*/
}
bool Mtable::TTrainParameters::RewindTimeTable(std::string actualStationName) {
if( actualStationName.compare( 0, 19, "PassengerStopPoint:" ) == 0 ) {
actualStationName = ToLower( actualStationName.substr( 19 ) );
}
for( auto i = 1; i <= StationCount; ++i ) {
// przechodzimy po całej tabelce i sprawdzamy nazwy stacji (bez pierwszej)
if (ToLower(TimeTable[i].StationName) == actualStationName) {
// nazwa stacji zgodna więc ustawiamy na poprzednią, żeby w następnym kroku poprawnie obsłużyć
StationIndex = i;
NextStationName = TimeTable[ i ].StationName;
TTVmax = TimeTable[ i ].vmax;
return true; // znaleźliśmy więc kończymy
}
}
// failed to find a match
return false;
}
void TTrainParameters::StationIndexInc()
{ // przejście do następnej pozycji StationIndex<=StationCount
++StationIndex;
}
bool TTrainParameters::IsTimeToGo(double hh, double mm)
// sprawdzenie, czy można już odjechać z aktualnego zatrzymania
// StationIndex to numer następnego po dodarciu do aktualnego
{
if ((StationIndex < 1))
return true; // przed pierwszą jechać
else if ((StationIndex < StationCount))
{ // oprócz ostatniego przystanku
if ((TimeTable[StationIndex].Ah < 0)) // odjazd z poprzedniego
return true; // czas przyjazdu nie był podany - przelot
else
return CompareTime(hh, mm, TimeTable[StationIndex].Dh, TimeTable[StationIndex].Dm) <= 0;
}
else // gdy rozkład się skończył
return false; // dalej nie jechać
}
// returns: difference between specified time and scheduled departure from current stop, in seconds
double TTrainParameters::seconds_until_departure( double const Hour, double const Minute ) const {
if( ( TimeTable[ StationStart ].Ah < 0 ) ) { // passthrough
return 0;
}
return ( 60.0 * CompareTime( Hour, Minute, TimeTable[ StationStart ].Dh, TimeTable[ StationStart ].Dm ) );
}
std::string TTrainParameters::ShowRelation() const
/*zwraca informację o relacji*/
{
// if (Relation1=TimeTable[1].StationName) and (Relation2=TimeTable[StationCount].StationName)
if ((Relation1 != "") && (Relation2 != ""))
return Relation1 + " - " + Relation2;
else
return "";
}
TTrainParameters::TTrainParameters(std::string const &NewTrainName)
/*wstępne ustawienie parametrów rozkładu jazdy*/
{
NewName(NewTrainName);
}
void TTrainParameters::NewName(std::string const &NewTrainName)
/*wstępne ustawienie parametrów rozkładu jazdy*/
{
TrainName = NewTrainName;
StationCount = 0;
StationIndex = 0;
StationStart = 0;
NextStationName = "nowhere";
LastStationLatency = 0;
Direction = 1;
Relation1 = "";
Relation2 = "";
for (int i = 0; i < MaxTTableSize + 1; ++i)
{
TimeTable[ i ] = TMTableLine();
}
TTVmax = 100; /*wykasowac*/
BrakeRatio = 0;
LocSeries = "";
LocLoad = 0;
}
void TTrainParameters::UpdateVelocity(int StationCount, double vActual)
// zapisywanie prędkości maksymalnej do wcześniejszych odcinków
// wywoływane z numerem ostatniego przetworzonego przystanku
{
int i = StationCount;
// TTVmax:=vActual; {PROWIZORKA!!!}
while ((i >= 0) && (TimeTable[i].vmax == -1))
{
TimeTable[i].vmax = vActual; // prędkość dojazdu do przystanku i
--i; // ewentualnie do poprzedniego też
}
}
// bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax)
//{
// return false;
//}
bool TTrainParameters::LoadTTfile(std::string scnpath, int iPlus, double vmax)
// wczytanie pliku-tabeli z rozkładem przesuniętym o (fPlus); (vMax) nie ma znaczenia
{
std::string lines;
std::string s;
std::ifstream fin;
bool EndTable;
double vActual;
int ConversionError = 0;
EndTable = false;
if ((TrainName == ""))
{ // jeśli pusty rozkład
// UpdateVelocity(StationCount,vMax); //ograniczenie do prędkości startowej
}
else
{
ConversionError = 666;
vActual = -1;
s = scnpath + TrainName + ".txt";
replace_slashes(s);
// Ra 2014-09: ustalić zasady wyznaczenia pierwotnego pliku przy przesuniętych rozkładach
// (kolejny pociąg dostaje numer +2)
fin.open(s.c_str()); // otwieranie pliku
replace_slashes(s);
if (!fin.is_open())
{ // jeśli nie ma pliku
vmax = atoi(TrainName.c_str()); // nie ma pliku ale jest liczba
if ((vmax > 10) && (vmax < 200))
{
TTVmax = vmax; // Ra 2014-07: zamiast rozkładu można podać Vmax
UpdateVelocity(StationCount, vmax); // ograniczenie do prędkości startowej
ConversionError = 0;
}
else
ConversionError = -8; /*Ra: ten błąd jest niepotrzebny*/
}
else
{ /*analiza rozkładu jazdy*/
ConversionError = 0;
while (fin.good() && !((ConversionError != 0) || EndTable))
{
std::getline(fin, lines); /*wczytanie linii*/
if (contains( lines, "___________________") ) /*linia pozioma górna*/
{
fin >> s;
if (s == "[") /*lewy pion*/
{
fin >> s;
if (s == "Rodzaj") /*"Rodzaj i numer pociagu"*/
do
{
fin >> s;
} while (!(s == "|") || (fin.eof())); /*środkowy pion*/
}
}
else if (lines == "")
{
fin.close();
break;
}
fin >> s; /*nazwa pociągu*/
// if LowerCase(s)<>ExtractFileName(TrainName) then {musi być taka sama, jak nazwa
// pliku}
// ConversionError:=-7 {błąd niezgodności}
TrainName = s; // nadanie nazwy z pliku TXT (bez ścieżki do pliku)
while (fin >> s || fin.bad())
{
if (contains( s,"_______|") )
{
break;
}
if (s == "Kategoria")
{
do
{
fin >> s;
} while (!((s == "|") || (fin.bad())));
fin >> TrainCategory;
continue;
}
if (s == "Nazwa")
{
do
{
fin >> s;
} while (!((s == "|") || (fin.bad())));
fin >> TrainLabel;
continue;
}
} // while (!(s == "Seria"));
// else
{ /*czytaj naglowek*/
while (fin >> s || !fin.bad())
{
if (s == "[")
break;
} // while (!() || (fin.eof())); /*pierwsza linia z relacją*/
while (fin >> s || !fin.bad())
{
if (s != "|")
break;
} // while (!(() || fin.eof()));
if( s != "|" ) {
Relation1 = s;
win1250_to_ascii( Relation1 );
}
else
ConversionError = -5;
while (fin >> s || !fin.bad())
{
if (s == "Relacja")
break;
} // while (
// !( || (fin.eof()))); /*druga linia z relacją*/
while (fin >> s || !fin.bad())
{
if (s == "|")
break;
} // while (!( || (fin.eof())));
fin >> Relation2;
win1250_to_ascii( Relation2 );
while (fin >> s || !fin.bad())
{
if (s == "Wymagany")
break;
} // while (!();
while (fin >> s || !fin.bad())
{
if ((s == "|") || (s == "\n"))
break;
} // while (!());
fin >> s;
s = s.substr(0, s.find("%"));
BrakeRatio = atof(s.c_str());
while (fin >> s || fin.bad())
{
if (s == "Seria")
break;
} // while (!(s == "Seria"));
do
{
fin >> s;
} while (!((s == "|") || (fin.bad())));
fin >> LocSeries;
fin >> LocLoad; // = s2rE(ReadWord(fin));
do
{
fin >> s;
} while (!(contains( s,"[______________" ) || fin.bad()));
auto activeradiochannel{ -1 };
while (!fin.bad() && !EndTable)
{
++StationCount;
do
{
fin >> s;
} while (!((s == "[") || (fin.bad())));
TMTableLine *record = &TimeTable[StationCount];
{
if (s == "[")
fin >> s;
else
ConversionError = -4;
if (false == contains( s,"|") )
{
record->km = atof(s.c_str());
fin >> s;
}
if (contains( s,"|_____|")) /*zmiana predkosci szlakowej*/
UpdateVelocity(StationCount, vActual);
else
{
fin >> s;
if (false == contains(s,"|"))
vActual = atof(s.c_str());
}
while (false == contains( s,"|"))
fin >> s;
fin >> record->StationName;
// get rid of non-ascii chars. TODO: run correct version based on locale
win1250_to_ascii( record->StationName );
do
{
fin >> s;
} while (!((s == "1") || (s == "2") || fin.bad()));
record->TrackNo = atoi(s.c_str());
fin >> s;
if (s != "|")
{
if (contains( s, hrsd) )
{
record->Ah = atoi( s.substr(0, s.find(hrsd)).c_str()); // godzina przyjazdu
record->Am = atof(s.substr(s.find(hrsd) + 1, s.length()).c_str()); // minuta przyjazdu
}
else
{
record->Ah = TimeTable[StationCount - 1].Ah; // godzina z poprzedniej pozycji
record->Am = atof(s.c_str()); // bo tylko minuty podane
}
}
do
{
fin >> s;
} while (!((s != "|") || (fin.bad())));
if (s != "]")
record->tm = atof(s.c_str());
do
{
fin >> s;
} while (!((s == "[") || fin.bad()));
fin >> s;
if (false == contains(s,"|"))
{
/*tu s moze byc miejscem zmiany predkosci szlakowej*/
fin >> s;
}
if (s.find("|_____|") !=
std::string::npos) /*zmiana predkosci szlakowej*/
UpdateVelocity(StationCount, vActual);
else
{
fin >> s;
if (false == contains(s,"|"))
vActual = atof(s.c_str());
}
while (false == contains(s,"|"))
fin >> s;
// stationware. added fix for empty entry
fin >> s;
while( false == ( ( s == "1" )
|| ( s == "2" )
|| fin.bad() ) ) {
record->StationWare += s;
fin >> s;
}
// cache relevant station data
record->is_maintenance = ( contains( s, "pt" ) );
{
auto const stationware { Split( record->StationWare, ',' ) };
for( auto const &entry : stationware ) {
if( entry.front() != 'R' ) {
continue;
}
auto const entrysplit { split_string_and_number( entry ) };
if( ( entrysplit.first == "R" )
&& ( entrysplit.second <= 10 ) ) {
auto const radiochannel { entrysplit.second };
if( ( record->radio_channel == -1 )
|| ( radiochannel != activeradiochannel ) ) {
// if the station has more than one radiochannel listed,
// it generally means we should switch to the one we weren't using so far
// TODO: reverse this behaviour (keep the channel used so far) once W28 signs are included in the system
record->radio_channel = radiochannel;
}
}
}
if( record->radio_channel != -1 ) {
activeradiochannel = record->radio_channel;
}
}
record->TrackNo = atoi(s.c_str());
fin >> s;
if (s != "|")
{
if (contains( s, hrsd) )
{
record->Dh = atoi(s.substr(0, s.find(hrsd)).c_str()); // godzina odjazdu
record->Dm = atof(s.substr(s.find(hrsd) + 1, s.length()).c_str()); // minuta odjazdu
}
else
{
record->Dh = TimeTable[StationCount - 1].Dh; // godzina z poprzedniej pozycji
record->Dm = atof(s.c_str()); // bo tylko minuty podane
}
}
else
{
record->Dh = record->Ah; // odjazd o tej samej, co przyjazd (dla ostatniego też)
record->Dm = record->Am; // bo są używane do wyliczenia opóźnienia po dojechaniu
}
do
{
fin >> s;
} while (!((s != "|") || (fin.bad())));
if (s != "]")
record->tm = atof(s.c_str());
do
{
fin >> s;
} while (!(contains( s, "[" ) || fin.bad()));
if (false == contains( s, "_|_"))
fin >> s;
if (false == contains( s, "|"))
{
/*tu s moze byc miejscem zmiany predkosci szlakowej*/
fin >> s;
}
if (contains( s, "|_____|") ) /*zmiana predkosci szlakowej*/
UpdateVelocity(StationCount, vActual);
else
{
fin >> s;
if (false == contains( s, "|"))
vActual = atof(s.c_str());
}
while (false == contains( s, "|" ) )
fin >> s;
while ((false == contains( s,"]") ))
fin >> s;
if (contains( s,"_|_") )
EndTable = true;
} /*timetableline*/
}
}
} /*while eof*/
fin.close();
}
}
if (ConversionError == 0)
{
if ((TimeTable[1].StationName == Relation1)) // jeśli nazwa pierwszego zgodna z relacją
if ((TimeTable[1].Ah < 0)) // a nie podany czas przyjazdu
{ // to mamy zatrzymanie na pierwszym, a nie przelot
TimeTable[1].Ah = TimeTable[1].Dh;
TimeTable[1].Am = TimeTable[1].Dm;
}
}
//
load_sounds();
// potentially offset table times
auto const timeoffset { static_cast<int>( Global.ScenarioTimeOffset * 60 ) + iPlus };
if( timeoffset != 0 ) // jeżeli jest przesunięcie rozkładu
{
long i_end = StationCount + 1;
float adjustedtime; // do zwiększania czasu
for (auto i = 1; i < i_end; ++i) // bez with, bo ciężko się przenosi na C++
{
if ((TimeTable[i].Ah >= 0))
{
adjustedtime = clamp_circular<float>( TimeTable[i].Ah * 60 + TimeTable[i].Am + timeoffset, 24 * 60 ); // nowe minuty
TimeTable[i].Am = (int(60 * adjustedtime) % 3600) / 60.f;
TimeTable[i].Ah = int((adjustedtime) / 60) % 24;
}
if ((TimeTable[i].Dh >= 0))
{
adjustedtime = clamp_circular<float>( TimeTable[i].Dh * 60 + TimeTable[i].Dm + timeoffset, 24 * 60 ); // nowe minuty
TimeTable[i].Dm = (int(60 * adjustedtime) % 3600) / 60.f;
TimeTable[i].Dh = int((adjustedtime) / 60) % 24;
}
}
}
return ConversionError == 0;
}
void
TTrainParameters::load_sounds() {
for( auto stationidx = 1; stationidx < StationCount + 1; ++stationidx ) {
auto &station { TimeTable[ stationidx ] };
if( station.Ah == -1 ) {
continue;
}
// specified arrival time means it's a scheduled stop
auto const stationname { (
ends_with( station.StationName, "_po" ) ?
station.StationName.substr( 0, station.StationName.size() - 3 ) :
station.StationName ) };
auto const lookup {
FileExists(
{ Global.asCurrentSceneryPath + stationname, std::string{ szSoundPath } + "sip/" + stationname },
{ ".ogg", ".flac", ".wav" } ) };
if( lookup.first.empty() ) {
continue;
}
// wczytanie dźwięku odjazdu w wersji radiowej (słychać tylko w kabinie)
station.name_sound =
sound_source{ sound_placement::engine, EU07_SOUND_CABANNOUNCEMENTCUTOFFRANGE }
.deserialize( lookup.first + lookup.second, sound_type::single );
}
}
bool TTrainParameters::DirectionChange()
// sprawdzenie, czy po zatrzymaniu wykonać kolejne komendy
{
if ((StationIndex > 0) && (StationIndex < StationCount)) // dla ostatniej stacji nie
if (contains( TimeTable[StationIndex].StationWare, '@') )
return true;
return false;
}
void TTrainParameters::serialize( dictionary_source *Output ) const {
Output->insert( "trainnumber", TrainName );
Output->insert( "traincategory", TrainCategory );
Output->insert( "trainname", TrainLabel );
Output->insert( "train_brakingmassratio", BrakeRatio );
Output->insert( "train_enginetype", LocSeries );
Output->insert( "train_engineload", LocLoad );
Output->insert( "train_stationfrom", Relation1 );
Output->insert( "train_stationto", Relation2 );
Output->insert( "train_stationindex", StationIndex );
Output->insert( "train_stationcount", StationCount );
Output->insert( "train_stationstart", StationStart );
if( StationCount > 0 ) {
// timetable stations data, if there's any
for( auto stationidx = 1; stationidx <= StationCount; ++stationidx ) {
auto const stationlabel { "train_station" + std::to_string( stationidx ) + "_" };
auto const &timetableline { TimeTable[ stationidx ] };
Output->insert( ( stationlabel + "name" ), Bezogonkow( timetableline.StationName ) );
Output->insert( ( stationlabel + "fclt" ), Bezogonkow( timetableline.StationWare ) );
Output->insert( ( stationlabel + "lctn" ), timetableline.km );
Output->insert( ( stationlabel + "vmax" ), timetableline.vmax );
Output->insert( ( stationlabel + "ah" ), timetableline.Ah );
Output->insert( ( stationlabel + "am" ), timetableline.Am );
Output->insert( ( stationlabel + "dh" ), timetableline.Dh );
Output->insert( ( stationlabel + "dm" ), timetableline.Dm );
Output->insert( ( stationlabel + "tracks" ), timetableline.TrackNo );
}
}
}
void TMTableTime::UpdateMTableTime(double deltaT)
// dodanie czasu (deltaT) w sekundach, z przeliczeniem godziny
{
mr += deltaT; // dodawanie sekund
while (mr >= 60.0) // przeliczenie sekund do właściwego przedziału
{
mr -= 60.0;
++mm;
}
while (mm > 59) // przeliczenie minut do właściwego przedziału
{
mm -= 60;
++hh;
}
while (hh > 23) // przeliczenie godzin do właściwego przedziału
{
hh -= 24;
++dd; // zwiększenie numeru dnia
}
}

120
world/mtable.h Normal file
View File

@@ -0,0 +1,120 @@
/*
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/.
*/
#pragma once
#include <string>
#include "Classes.h"
#include "sound.h"
namespace Mtable
{
static int const MaxTTableSize = 100; // można by to robić dynamicznie
static char const *hrsd = ".";
// Ra: pozycja zerowa rozkładu chyba nie ma sensu
// Ra: numeracja przystanków jest 1..StationCount
struct TMTableLine
{
float km{ 0.f }; // kilometraz linii
float vmax{ -1.f }; // predkosc rozkladowa przed przystankiem
// StationName:string[32]; //nazwa stacji ('_' zamiast spacji)
// StationWare:string[32]; //typ i wyposazenie stacji, oddz. przecinkami}
std::string StationName{ "nowhere" }; // nazwa stacji ('_' zamiast spacji)
std::string StationWare; // typ i wyposazenie stacji, oddz. przecinkami}
int TrackNo{ 1 }; // ilosc torow szlakowych
int Ah{ -1 };
float Am{ -1.f }; // godz. i min. przyjazdu, -1 gdy bez postoju
int Dh{ -1 };
float Dm{ -1.f }; // godz. i min. odjazdu
float tm{ 0.f }; // czas jazdy do tej stacji w min. (z kolumny)
bool is_maintenance{ false };
int radio_channel{ -1 };
sound_source name_sound{ sound_placement::engine };
};
typedef TMTableLine TMTable[MaxTTableSize + 1];
// typedef TTrainParameters *PTrainParameters;
class TTrainParameters
{
public:
std::string TrainName;
std::string TrainCategory;
std::string TrainLabel;
double TTVmax;
std::string Relation1;
std::string Relation2; // nazwy stacji danego odcinka
double BrakeRatio;
std::string LocSeries; // seria (typ) pojazdu
double LocLoad;
TMTable TimeTable;
int StationCount; // ilość przystanków (0-techniczny)
int StationIndex; // numer najbliższego (aktualnego) przystanku
int StationStart; // numer pierwszej stacji pokazywanej na podglądzie rozkładu
std::string NextStationName;
double LastStationLatency;
int Direction; /*kierunek jazdy w/g kilometrazu*/
double CheckTrainLatency();
/*todo: str hh:mm to int i z powrotem*/
std::string ShowRelation() const;
double WatchMTable(double DistCounter);
std::string NextStop() const;
sound_source next_stop_sound() const;
sound_source last_stop_sound() const;
bool IsStop() const;
bool IsLastStop() const;
bool IsMaintenance() const;
bool IsTimeToGo(double hh, double mm);
// returns: difference between specified time and scheduled departure from current stop, in seconds
double seconds_until_departure( double const Hour, double const Minute ) const;
bool UpdateMTable(double hh, double mm, std::string const &NewName);
bool UpdateMTable( scenario_time const &Time, std::string const &NewName );
bool RewindTimeTable( std::string actualStationName );
TTrainParameters( std::string const &NewTrainName = "none" );
void NewName(std::string const &NewTrainName);
void UpdateVelocity(int StationCount, double vActual);
bool LoadTTfile(std::string scnpath, int iPlus, double vmax);
bool DirectionChange();
void StationIndexInc();
void serialize( dictionary_source *Output ) const;
// returns: radio channel associated with current station, or -1
int radio_channel() const;
// returns: sound file associated with current station, or -1
sound_source current_stop_sound() const;
private:
void load_sounds();
};
class TMTableTime
{
public:
int dd = 0;
int hh = 0;
int mm = 0;
double mr = 0.0;
void UpdateMTableTime(double deltaT);
TMTableTime(int InitH, int InitM ) :
hh( InitH ),
mm( InitM )
{}
TMTableTime() = default;
};
}
#if !defined(NO_IMPLICIT_NAMESPACE_USE)
using namespace Mtable;
#endif

89
world/station.cpp Normal file
View File

@@ -0,0 +1,89 @@
/*
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 "station.h"
#include "DynObj.h"
#include "mtable.h"
namespace simulation {
basic_station Station;
}
// exchanges load with consist attached to specified vehicle, operating on specified schedule
double
basic_station::update_load( TDynamicObject *First, Mtable::TTrainParameters &Schedule, int const Platform ) {
// TODO: filter out maintenance stops when determining first and last stop
auto const firststop { Schedule.StationIndex == 1 };
auto const laststop { Schedule.StationIndex == Schedule.StationCount };
// HACK: determine whether current station is a (small) stop from the presence of "po" at the name end
auto const stationname { Schedule.TimeTable[ Schedule.StationIndex ].StationName };
auto const stationequipment { Schedule.TimeTable[ Schedule.StationIndex ].StationWare };
auto const trainstop { (
( ends_with( stationname, "po" ) )
|| ( contains( stationequipment, "po" ) ) ) };
auto const maintenancestop { ( contains( stationequipment, "pt" ) ) };
// train stops exchange smaller groups than regular stations
// NOTE: this is crude and unaccurate, but for now will do
auto const stationsizemodifier { ( trainstop ? 1.0 : 2.0 ) };
// go through all vehicles and update their load
// NOTE: for the time being we limit ourselves to passenger-carrying cars only
auto exchangetime { 0.f };
auto *vehicle { First };
while( vehicle != nullptr ) {
auto &parameters { *vehicle->MoverParameters };
if( parameters.Doors.range == 0.f ) { goto next; }
if( parameters.LoadType.name.empty() ) {
// (try to) set the cargo type for empty cars
parameters.LoadAmount = 0.f; // safety measure against edge cases
parameters.AssignLoad( "passengers" );
parameters.ComputeMass();
}
if( parameters.LoadType.name == "passengers" ) {
// NOTE: for the time being we're doing simple, random load change calculation
// TODO: exchange driven by station parameters and time of the day
auto unloadcount = static_cast<int>(
TestFlag( parameters.DamageFlag, dtrain_out ) ? parameters.LoadAmount :
laststop ? parameters.LoadAmount :
firststop ? 0 :
maintenancestop ? 0 :
std::min<float>(
parameters.LoadAmount,
Random( parameters.MaxLoad * 0.15f * stationsizemodifier ) ) );
auto loadcount = static_cast<int>(
TestFlag( parameters.DamageFlag, dtrain_out ) ? 0 :
laststop ? 0 :
maintenancestop ? 0 :
Random( parameters.MaxLoad * 0.15f * stationsizemodifier ) );
if( true == firststop ) {
// larger group at the initial station
loadcount *= 2;
}
if( ( unloadcount > 0 ) || ( loadcount > 0 ) ) {
vehicle->LoadExchange( unloadcount, loadcount, Platform );
exchangetime = std::max( exchangetime, vehicle->LoadExchangeTime() );
}
}
next:
vehicle = vehicle->Next();
}
return exchangetime;
}

29
world/station.h Normal file
View File

@@ -0,0 +1,29 @@
/*
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/.
*/
#pragma once
#include "Classes.h"
// a simple station, performs freight and passenger exchanges with visiting consists
class basic_station {
public:
// methods
// exchanges load with consist attached to specified vehicle, operating on specified schedule; returns: time needed for exchange, in seconds
double
update_load( TDynamicObject *First, Mtable::TTrainParameters &Schedule, int const Platform );
};
namespace simulation {
extern basic_station Station; // temporary object, for station functionality tests
} // simulation