Класс Vehicle: полное описание

Раздел посвящен обсуждению вопросов разработки DLL-модулей подвижного состава
Ответить
Аватара пользователя
maisvendoo
Модератор
Сообщения: 162
Зарегистрирован: 13 авг 2019, 10:25
Город: Ростов-на-Дону
Настоящее имя: Дмитрий
VK: https://vk.com/maisvendoo
Контактная информация:

Класс Vehicle: полное описание

Сообщение maisvendoo » 01 мар 2020, 17:31

Думаю будет полезно описать подробно основные классы, с которыми работает физический движок RRS. И начать нужно с класса Vehicle, являющегося основой для разработки подвижного состава.

С интерфейсом этого класса можно ознакомится в в заголовочном файле sdk/include/vehicle.h который идет в комплекте с игрой. Класс этот довольно объемный, я бы сказал монструозный. Данный класс описывает так называемую единицу подвижного состава или подвижную единицу (ПЕ). Под единицей подвижного состава в симуляторе понимается железнодорожный экипаж, имеющий свой кузов, ходовую часть и внутреннее оборудование. Это может быть секция локомотива, вагон МВПС, несамоходный вагон.

Ещё раз повторю ключевую особенность RRS - в этом симуляторе все подвижные единицы динамически равноправны. Это означает, простым языком, что вагон с точки зрения движка игры, ничем не оличается от секции локомотива. То есть вообще ничем. Движок игры оперирует с классом Vehicle, получая от него текущую величину ускорения кузова. Какое внутреннее оборудование порождает это ускорение движок игры слыхом не слыхивал, все это остается на совести разработчика DLL-модуля подвижной единицы.

Класс Vehicle наследует от Qt-класса QObject, для того, чтобы использовать все ключевые возможности фреймворка Qt, на котором основана игра, в частности для использования механизма передачи сигналов от одного класа другому.

Поскольку класс довольно большой, разобью описание его свойств и методов на несколько разделов.

1. Защищенные свойства класса Vehicle
  • idx - индекс подвижной единиы, то есть номер, с которого начинается часть вектора состояния поездаб относящаяся к данной ПЕ;
  • empty_mass - "сухая" масса, для вагоны это масса тары вагона, для локомотива - его масса в неэкипированном состоянии;
  • payload_mass - полная, подчеркиваю, полная, максимально возможная масса полезной нагрузки. Для вагона это его полная грузоподъемность, для локомотива - полная масса всех экипировочных материалов (топливо, вода, масло, песок и т.д.);
  • payload_coeff - коэффициент загрузки, то есть доля полной грузоподъемности. Этот коэффициент варьируется от 0.0 до 1.0 и характеризует степень загрузки данной ПЕ;
  • full_mass - полная масса подвижной единицы.
На полной массе остановлюсь подробнее. Она не задается непосрественно, а расчитывается исходя из заданных в конфигурационных файлах параметров. Масса тары и горузоподъемность задаются в конфиге ПЕ через параметры EmptyMass и PayloadMass. Коэффициент загрузки задается в конфиге поезда, куда включается данная подвижная единица через параметр PayloadCoeff индивидуально для каждой ПЕ или для группы ПЕ. Если все это задано, то при инициализации ПЕ её масса расчитывается по формуле

full_mass = empty_mass + payload_coeff * payload_mass

причем payload_coeff программно ограничивается в диапазоне от 0.0 до 1.0. Так что схитрить и задать в конфиге этот параметр равным 1000 не выйдет :-)

Идем далее
  • length - длина ПЕ между осями автосцепок;
  • num_axis - число осей;
  • J_axis - момент инерции колесной пары относительно оси вращения;
  • wheel_diameter - диаметр колеса по кругу катания
  • rk - радиус колеса, он не задается из конфига, а вычисляется путем деления wheel_diameter пополам.
  • R1 - усилие в переднем сцепном приборе;
  • R2 - усилие в заднем сцепном приборе;
  • railway_coord0 - начальная координата ПЕ;
  • railway_coord - текущая координата ПЕ;
  • velocity - текущая скорость ПЕ;
  • wheel_rotation_angle - массив, число элементов которого равно num_axis, содержащий углы поворота колесных пар;
  • wheel_omega - массив размера num_axis, сожержит угловые скорости вращения колесных пар;
  • b0, b1, b2, b3, q0 - коэффициенты формулы удельного сопротивления движению;
Не секрет, что на любое транспортное средства действуют силы сопротивления, стремящиеся его замедлить. Железнодорожный экипаж не исключение. Сопротивление движению оказывает трение качения КП по рельсам, трение в буксовых узлах, сопротивление воздушной среды, обтекающей кузов. Все эти факторы в отдельности сложно учесть, поэтому их учитывают в комплексе вводя понятие удельной, то есть отнесенной к единице массы экипажа, силы сопротивления движению. Эта удельная сила вычисляется по формуле

w = b0 + (b1 + b2 * V + b3 * V * V) / q0

где V - скорость экипажа в км/ч. Сама сила, в справочнике Гребенюка имеет размерность килограмм силы на тонну массы, то есть кгс/т. Коэффициенты этой формулы b0 - b3 определяются экспериментально для каждой серии подвижного состава по методике ВНИИЖТ. Есть замечательная книга-справочник "Тяговые расчеты" за авторством П.Т. Гребенюка, где можно получить эти коэффициенты для вагонов и локомотивов.

Для того чтобы расчитать силу сопротивления, вычисляется удельное сопротивление по этой формуле, а затем пересчитать в силу действующу на ПЕ, вырашенную уже в Ньютонах

W = full_mass * Physics::g * w / 1000.0

где Physics::g - константа движка, в которой хранится значение ускорения свободного падения.

Продолжим
  • inc - уклон профиля пути, в тысяных, на котором находится в данный момент ПЕ;
  • curv - кривизна пути в плане, численно равна еденице, деленной на радиус кривой;
По уклону определяют составляющую силы тяжести, действующую на уклоне

G = full_mass * Physics::g * inc / 1000.0

а по кривизне пути, удельное сопротивление движению в кривой

wk = 700 * curv
  • dir - направление движения поезда. Равно 1 если рабочая кабина локомотива/головного вагона направлена в сторону увеличения координаты, и -1 если в сторону уменьшения.
  • p0 - давление в начале тормозной магистрали. Необходимо для корректной работы кранов машиниста;
  • auxRate - темп дополнительной разрядки тормозной магистрали, МПа/с. Служит для дополнительной разрядки ТМ от воздухораспределителя
  • pTM - текущее давление в тормозной магистрали данной ПЕ;
  • DebugMsg - строка типа QString, содержащая отладочное сообщение выводимое внизу экрана по нажатию F1.
В отладочною строку можно написать любую чушь, и она будет выводится на экран. Очень удобно использовать для отладки DLL-модуля ПЕ.
  • prev_vehicle - укзазатель типа Vehicle на предыдущу ПЕ, вцепленную в поезд перед данной ПЕ;
  • next_vehicle - указатель на следующую ПЕ, вцепленную в поезд сразу за нашей.
Эта пара указателей используется при работе ЭПТ, в будущем будет использоваться для работы раздельной объемной тормозной магистрали, а так же для создания многосекционных локомотивов и МВПС.
  • config_dir - строка типа QString, в которой хранится полный путь к папке с конфигурационными файлами данное ПЕ. Автоматически определяется симулятором, служит для удобного доступа к конфигам из DLL-модуля.
  • Uks - напряжение в контактной сети;
  • current_kind - род тока в контактной сети: 1 - переменный ток; 2 - постоянный ток;
Под Uks понимается действующее значение напряжения в контактном проводе. На системе переменного тока колеи 1520 мм оно может находится в пределах от 19 до 29 киловольт. На системе постоянного тока - от 2,2 до 4,0 кВ.

Под current_kind понимается именно род электрического тока, переменный синусоидальный или постоянный. Устройства контроля рода тока должны реагировать именно на значения этого параметра.
  • Q_a - вектор обобщенных активных сил размером num_axis + 1;
  • Q_r - вектор обощенных диссипативных (тормозных) сил размером num_axis + 1;
  • a - вектор обощенных ускорений размером num_axis + 1;
a[0] - продольное ускорение кузова ПЕ, a[1] - a[num_axis] - угловые ускорения колесных пар. Соотвественно Q_a[0] - линейная движущая сила, развиваемая произвольным двигателем, приложенная к кузову подвижной единицы. В нормальных локомотивах и вагонах эта сила всегда равна нулю. Но если кто-то задумает реалировать поезд с реактивным двигателем (а такой был в СССР), то вот через эту силу и можно передать тягу на ракетный вагон :yes:

Q_a[1] - Q_a[num_axis] - крутящие моменты, передаваемые на колесные пары от тягового привода, в том числе и в режиме электродинамического торможения. Эти моменты задаются разработчиком DLL-модуля и являются входными параметрами, позволяющими воздействовать на динамику ПЕ и поезда в целом.

Q_r[0] - линейная тормозная сила. Может быть использована, например для реализации магнитно-рельсового тормоза.

Q_r[1] - Q_r[num_axis] - тормозные моменты, развиваемые фрикционными тормозами и передаваемые на ось колесной пары. Через эти момент реализуется действие тормозов.
  • keys - массив, содержащий состояние клавиатуры, по каждой клавише;
  • analogSignals - массив сигналов из 200 элементов, через который DLL-модуль воздействует на визуальную модель локомотива, управляя анимациями;
  • control_signals - массив сигналов, принимаемых от внешнего аппаратного пульта;
  • feedback_signals - массив сигналов, передаваемых на внешний аппаратный пульт;
  • ept_control - массив линий управления ЭПТ;
  • ept_current - массив токов в линиях управления ЭПТ;
Число линий управления ЭПТ задается самим разработчиком DLL-модуля. Сигналы на этих линиях (-1 - напряжение обратной полярности; 0 - отсутствие напряжения; 1 - сигнал прямой полярности) подключаются к соответствующим клеммам на электровоздухораспределителе (ЭВР)

Все эти параметры доступны в классе ПЕ, унаследованном от Vehicle. Все они, кроме крутящих и тормозных моментов, задаются либо в конфигах, либо рассчитываются самим симулятором, что, безусловно, не исключает и воздействия на них со стороны разработчика дополнения.

Пока остановлюсь на этом, но продолжение следует...
Возврата к деспотии Ситхов не будет!

Аватара пользователя
maisvendoo
Модератор
Сообщения: 162
Зарегистрирован: 13 авг 2019, 10:25
Город: Ростов-на-Дону
Настоящее имя: Дмитрий
VK: https://vk.com/maisvendoo
Контактная информация:

Re: Класс Vehicle: полное описание

Сообщение maisvendoo » 01 мар 2020, 21:47

2. Защищенные виртуальные методы

Перейдем к описанию методов, и начнем с тех, которые реализуются разработчиком DLL, а точнее могут быть им переопределены. Эти методы являются методами обратного вызова, то есть реализуются они разработчиком, а вызваются симулятором, в те моменты, когда это необходимо. Причем вызываются они самим классом Vehicle, поэтому имеют модификатор доступа protected.
  • void initialization() - метод, вызываемый при инициализации подвижной единицы. Здесь выполняется начальная инициализация всего оборудования, которое содержит экипаж. Этот метод вызывается после загрузки DLL-модуля и чтения его конфигурационного файла.
  • void loadConfig(QString cfg_path) - загрузка параметров из конфигурационного файла подвижной единицы. При вызове, через параметр cfg_path в метод симулятором передается полный путь к конфигурационному файлу. Этот метод используется исключительно для загрузки параметров конфига, введенных разработчиком, то есть тех, которые не содержаться в базовом классе Vehicle. Те переметры, которые определены в классе Vehicle читаются автоматически.
  • void preStep(double t) - действия, выполняемые перед шагом интегрирования уравнений движения поезда. Симулятором сюда передается текущее модельное время в секундах, прошетшее с момента запуска игры.
  • void step(double t, double dt) - вызывается на каждом шаге интегрирования уравнений движения поезда, из симулятора сюда передается текущее время t и текущий шаг интегрирования dt.
  • void postStep(double t) - действия, выполняемые после шага интегрирования уравнений движения поезда.
  • void hardwareOutput() - вывод сигналов во внешний пульт управления. Отсюда можно двигать стрелки и зажигать контрольные лампы на пульте домашнего тренажера, подключенного к симулятору.
Подытоживая, можно пояснить, что любое устройство, технический объект, создавая его компьютерную модель, в том числе локомотив или вагон, можно описать системой дифференциальных и алгебраических уравнений. Так вот, методы preStep() и postStep() следует использовать для решения алгебраических уравнений, а метод step() - для решения дифференциальных, описывающих разрабатываемый железнодорожный экипаж.

3. Публичные виртуальные методы

Эти методы вызываются другими классами, работающими с модулями подвижных единиц через интерфейс класса Vehcile, в частности они вызываются при работе класса Train, описывающего поезд. Поэтому они сделаны публичными. И хоть они и виртуальные, но их вобщем-то не обязательно переопределять. Но, для больше гибкости разработки решено было дать возможность разработчику DLL переопределить их.
  • state_vector_t getAcceleration(state_vector_t &Y, double t) - расчет обобщенных ускорений подвижной единицы. Ключевой метод, в котором и сидит физика движения железнодорожного экипажа. Здесь, на основании сил и моментов, приложенных к кузову и колесным парам экипажа, расчитывается продольное ускорение кузова и угловые ускорения колесных пар. Этот метод вызывается классом Train при решении уравнений движения поезда.
Если интересна реализация этого метода, то вот она

simulator/vehicle/vehicle.cpp

Код: Выделить всё

state_vector_t Vehicle::getAcceleration(state_vector_t &Y, double t)
{
    (void) t;

    // Получаем скорость нашей ПЕ из вектора состояния поезда
    double v = Y[idx + s];
    // Переводим модуль скорости в км/ч
    double V = abs(v) * Physics::kmh;

    // Расчитываем силу от продольного профиля пути
    double sin_beta = inc / 1000.0;
    double G = full_mass * Physics::g * sin_beta;

    // Расчитываем силу основного сопротивления движению
    double w = b0 + (b1 + b2 * V + b3 * V * V) / q0;
    double wk = 700.0 * curv;
    double W = full_mass * Physics::g * (w + wk) / 1000.0;


    // Расчитываем усилия, передаваемые от колесных пар на кузов
    double sumEqWheelForce = 0;

    for (size_t i = 1; i <= static_cast<size_t>(num_axis); i++)
    {
        double eqWheelForce = (Q_a[i] - Physics::fricForce(Q_r[i], dir * Y[idx + s + i])) / rk;
        sumEqWheelForce += eqWheelForce;
    }

    // Расчитываем полную силу сопротивления
    double Fr = Physics::fricForce(W + Q_r[0], dir * v);

    // Считаем продольное ускорение кузова
    *a.begin() = dir * (*Q_a.begin() - Fr + R1 - R2 + sumEqWheelForce - G) / ( full_mass + num_axis * J_axis / rk / rk);

    // Считаем угловые ускорения колесных пар
    auto end = a.end();
    for (auto accel_it = a.begin() + 1; accel_it != end; ++accel_it)
        *accel_it = *a.begin() / rk;

    return a;
}
Когда может потребоваться переопределить этот метод? Ну, например, если возникнет желание создать вагон, движущийся с учетом наличия жидкого груза, например цистерну, на движения которой влияют колебания жидкости в ней. Можно придумать много других ситуаций, вопрос в том, что для большинства задач переопределять этот метод не надо, и переопределяя его вы действете на свой страх и риск.

Другие методы этой группы более прозаичные
  • void keyProcess() - обработка нажатий клавиш на клавиатуре. Переопределять нужно, если какое-то оборудование, реализуемое вами не наследуется от класса Device (класс Device сам обрабатывает клавиатурный ввод) и требуется заставить его реагировать на управление с клавиатуры.
  • void hardwareProcess() - вывод сигналов на внешний пульт управления. Релизация этого метода тривиальна. Оставлен виртуальным, пока нет полной ястности с API взаимодействия с внешними пультами. Внутри этого метода вызывается защищеннй виртуальный метод hardwareOutput(), так что необходимости в его переопределении нет, возможно, в будущих версиях он перестанет быть виртуальным.
Особняком стоит следующий метод
  • void initBrakeDevices(double p0, double pTM, double pFL) - инициализация тормозных приборов. Передаются: p0 - зарядное давление ТМ; pTM - давление в ТМ данной подвижной единицы; pFL - давление в питательной магистрали (ПМ).
Зачем нужен этот метод? Почему его можно и нужно переопределять? Для того чтобы инициализировать тормозные приборы, когда локомотив или вагон в симуляторе стартует с полностью заряженными тормозами. Но об этом я расскажу отдельно.


3. Сигналы

Специальные методы, не имеющие реализации, используемые в Qt для воздействия из нашего класса на другие классы. При вызове сигнала, в другом классе-наследнике QObject сработает соотвествующий метод-слот и выполнит некие действия.
  • void logMessage(QString msg) - посылка сообщения в лог-файл. На момент версии 1.0.4 уже устарел, и в ближайшее время будет удален из API, так как уже в версии 1.0.4 работает система логирования Дмитрия Терещенко, работающая через синглтон Journal;
  • void soundPlay(QString name) - играть звук с именем name;
  • void soundStop(QString name) - выключить звук с именем name;
  • void soundSetVolume(QString name, int volume) - задать звуку с именем name уровень громкости volume (в процентах);
  • void soundSetPitch(QString name, float pitch) - задать частоту pitch воспроизведения звука name в долях от его нормальной частоты. Можно как понижать, так и повышать частоту проигрывания звука.
Как видно, сигналы используются для воздействия на звуковую подсистему из кода DLL-модуля ПЕ. Под именем звука понимается его символическое имя, прописанное в файле data/sounds/<имя ПЕ>/sounds.xml. Вот пример данного файла, взятый из электровоза ВЛ60пк

data/sounds/vl60pk-1543/sounds.xml

Код: Выделить всё

<?xml version="1.0" encoding="UTF-8"?>
<Config>
	
	<!-- Озвучка тумблеров с фиксацией -->

	<!-- Включение -->
	<Sound>
		<Name>K_Tumbler_On</Name>
		<Path>k-tumbler-on.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Выключение -->
	<Sound>
		<Name>K_Tumbler_Off</Name>
		<Path>k-tumbler-off.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Озвучка тумблеров с фиксацией -->

	<!-- Включение -->
	<Sound>
		<Name>K_Tumbler_Nofixed_On</Name>
		<Path>k-tumbler-nofixed-on.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Выключение -->
	<Sound>
		<Name>K_Tumbler_Nofixed_Off</Name>
		<Path>k-tumbler-nofixed-off.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>


	<!-- Включение ГВ -->
	<Sound>
		<Name>GV_On</Name>
		<Path>gvon.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Выключение ГВ -->
	<Sound>
		<Name>GV_Off</Name>
		<Path>gvoff.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>


	<!-- Подъем токоприемника -->
	<Sound>
		<Name>Pantograph_Up</Name>
		<Path>TPUp.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>


	<!-- Опускание токоприемника -->
	<Sound>
		<Name>Pantograph_Down</Name>
		<Path>TPDown.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>


	<!-- Работа тягового трансформатора -->
	<Sound>
		<Name>Trac_Transformer</Name>
		<Path>trans.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>10</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Работа расщепителя фаз  -->
	<Sound>
		<Name>Phase_Splitter</Name>
		<Path>fasan.wav</Path>
		<InitVolume>30</InitVolume>
		<MaxVolume>30</MaxVolume>
		<InitPitch>0.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Звуки работы мотор-вертиляторов -->
	
	<!-- МВ1 -->
	<Sound>
		<Name>Motor_Fan1</Name>
		<Path>vent.wav</Path>
		<InitVolume>15</InitVolume>
		<MaxVolume>15</MaxVolume>
		<InitPitch>0.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- МВ2 -->
	<Sound>
		<Name>Motor_Fan2</Name>
		<Path>vent.wav</Path>
		<InitVolume>20</InitVolume>
		<MaxVolume>20</MaxVolume>
		<InitPitch>0.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>		


	<!-- МВ3 -->
	<Sound>
		<Name>Motor_Fan3</Name>
		<Path>vent.wav</Path>
		<InitVolume>40</InitVolume>
		<MaxVolume>40</MaxVolume>
		<InitPitch>0.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- МВ4 -->
	<Sound>
		<Name>Motor_Fan4</Name>
		<Path>vent.wav</Path>
		<InitVolume>50</InitVolume>
		<MaxVolume>50</MaxVolume>
		<InitPitch>0.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- МВ5 -->
	<Sound>
		<Name>Motor_Fan5</Name>
		<Path>vent.wav</Path>
		<InitVolume>70</InitVolume>
		<MaxVolume>70</MaxVolume>
		<InitPitch>0.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- МВ6 -->
	<Sound>
		<Name>Motor_Fan6</Name>
		<Path>vent.wav</Path>
		<InitVolume>80</InitVolume>
		<MaxVolume>80</MaxVolume>
		<InitPitch>0.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Звуки работы мотор-компрессора -->	
	<Sound>
		<Name>Motor_Compressor</Name>
		<Path>compr.wav</Path>
		<InitVolume>50</InitVolume>
		<MaxVolume>50</MaxVolume>
		<InitPitch>0.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Щелчек рукоятки крана 395 -->
	<Sound>
		<Name>Kran_395_ruk</Name>
		<Path>395-chelk.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Щелчек комбинированного крана -->
	<Sound>
		<Name>Komb_kran</Name>
		<Path>komb-kran.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Щелчек комбинированного крана -->
	<Sound>
		<Name>UBT_367_ruk</Name>
		<Path>ubt-367-ruk.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>


	<!-- Серводвигатель ЭКГ -->
	<Sound>
		<Name>EKG_serv</Name>
		<Path>serv.wav</Path>
		<InitVolume>30</InitVolume>
		<MaxVolume>30</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Тяговый двигатель -->
	<Sound>
		<Name>TED</Name>
		<Path>ted_60-62.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>


	<!-- Скоростемер -->
	<Sound>
		<Name>Skorostemer</Name>
		<Path>skorostemer.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Свисток -->
	<Sound>
		<Name>Svistok</Name>
		<Path>VL60-svistok.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<!-- Свисток -->
	<Sound>
		<Name>Tifon</Name>
		<Path>VL60-tifon.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<Sound>
		<Name>KRM395_vpusk</Name>
		<Path>395_vpusk.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>1</PlayOnStart>
	</Sound>

	<Sound>
		<Name>KRM395_vipusk</Name>
		<Path>395_vypusk.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>1</PlayOnStart>
	</Sound>

	<Sound>
		<Name>KRM395_1</Name>
		<Path>395_1.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>1</PlayOnStart>
	</Sound>

	<Sound>
		<Name>KRM395_2</Name>
		<Path>395_2.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>25</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>1</PlayOnStart>
	</Sound>

	<Sound>
		<Name>KRM395_5</Name>
		<Path>395_5.wav</Path>
		<InitVolume>0</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>1</Loop>
		<PlayOnStart>1</PlayOnStart>
	</Sound>

	<Sound>
		<Name>EPT_On</Name>
		<Path>tumbler.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

	<Sound>
		<Name>EPT_Off</Name>
		<Path>tumbler.wav</Path>
		<InitVolume>100</InitVolume>
		<MaxVolume>100</MaxVolume>
		<InitPitch>1.0</InitPitch>
		<Loop>0</Loop>
		<PlayOnStart>0</PlayOnStart>
	</Sound>

</Config>
Каждая секция Sound в этом файле описывает отдельный звук. В ней связывается звуковой файл *.wav (параметр Path) с именем звука (параметр Name). Видно, что одному файлу может быть приведено в соотвествие несколько звуков, как например сделано это для мотор-вентиляторов. Звук-то по сути один и тот же, но играться он будет по-разному, в зависимости от того, с каким вентилятором он связан.

4. Приватные методых

Эти методы вызываются самим классом Vehicle, разработчик DLL-модуля не может их увидеть. Тем не менее, для понимания контекста вызова методов, доступных разработчику, полезно знать как называются и что делают данные методы.
  • void loadConfiguration(QString cfg_path) - чтение дефолтных параметров конфига ПЕ. Вызывается при нициализации подвижной единицы. В этом методе читаются такие параметры как EmptyMass, PaylodMass, Length, WheelDiameter, NumAxis, WheelInertia, MainResist. При это выполняется инициализация всех зависимых от перечисленных параметров переменных. После этой процедуры вызывается метод loadConfig(), переопределяемый разработчиком модуля для чтения собственных параметров.
  • void loadMainResist(QString cfg_path, QString main_resist_cfg) - чтение параметров формулы основного сопротивления движению.
Тут надо пояснить, что параметры формулы основного сопротивления движению, о которых мы говорили выше, читаются из отдельного файла, который хранится в каталоге игры cfg/main-resist/. Имя этого XML-файла указывается в параметре MainResist конфига ПЕ. Для чего это?

Отлаживали мы как-то на работе тренажер "Сапсана". Не хотел зараза разгонятся выше 150 км/ч, даже на площадке. Выяснилось, что ошибка в формуле основного сопротивления движению. Для каждого вагона того тренажера эти параметры задаются в конфиге вагона. А вагонов десять. А формула для всех вагонов одна. Я проклял всё, подбирая коэффициенты и правя 10 конфигов одновременно. Чтобы такого не повторялось, в RRS я сделал так. Допустим есть у нас пассажирский цельнометалический вагон. Его удельное основное сопротивлению движения одинаково для многих серий вагонов. А в RRS уже сейчас несколько серий таких вагонов, сделаных Романом Бирюковым. Допустим в форулу закралась ошибка. Что, править конфиги всех серий? Или лучше поправить один файл cfg/main-resist/passcar.xml и все дополнения автоматически получат исправленную формулу, так как вместо хранения коэффициентов, их конфиги просто ссылаются на нужную формулу. Наверное лучше второй вариант ;-)


5. Публичные методы

Эти методы вызываются движком игры, за них класс Train дергает каждую подвижную единицу, с целью выполнения ею каких-то вычислении или получения данных. Это интерфейсная часть, общая для всех потомков класса Vehicle. Их нельзя переопределить, более того, добавление своих публичных методов в модуле ПЕ не только бесполезно (так как симулятор ничего не знает о них), но и нежелательно. Все методы класса-наследника от Vehicle, создаваемые разработчиком модуля, должны иметь модификатор доступа private.

Итак, публичные методы класса Vehicle
  • void init(QString cfg_path) - иницалиация подвижной единицы. В качестве параметра движком игры сюда передается полный путь к конфигу данной подвижной единицы. В этом методе вызывается приватный метод loadConfiguration(), а так же защищенный виртуальный метод initialization().
  • void setIndex(size_t idx) - задать подвижной единице её индекс в векторе состояния поезда. Об этом чуть позже рассажу подробнее.
  • void setInclination(double inc) - задать уклон профиля пути.
  • void setCurvature(double curv) - задать кривизну пути в плане.
  • void setDirection(int dir) - задать направление движения.
  • void setForwardForce(double R1) - задать усилие в переднем сцепном приборе.
  • void setBackwardForce(double R2) - задать усилие в заднем сцепном приборе.
  • void setPayloadCoeff(double payload_coeff) - задать коэффициент загрузки.
  • void setRailwayCoord(double value) - задать координату ПЕ.
  • void setVelocity(double value) - задать скорость ПЕ.
  • void setWheelAngle(size_t i, double value) - задать угол поворота i-й колесной пары (нумеруются с нуля!)
  • void setWheelOmega(size_t i, double value) - задать угловую скорость i-й колесной пары.
  • void setPrevVehcile(Vehicle *vehicle) - задать указатель на подвижную единицу, прицепленную спереди.
  • void setNextVehcile(Vehicle *vehicle) - задать указатель на подвижную единицу, прицепленную сзади.
  • void setConfigDir(QString config_dir) - задать путь к конфигам данной ПЕ.
  • size_t getIndex() - получить индекс ПЕ.
  • double getMass() - получить полную массу ПЕ.
  • double getLtngth() - получить длину ПЕ по осям автосцепок.
  • double getWheelDiameter() - получить диаметр колеса по кругу катания.
  • double getRailwayCoord() - получить координату ПЕ.
  • double getVelocity() - получить скорость ПЕ.
  • double getWheelAngle(size_t i) - получить углол поворота i-й колесной пары
  • double getWheelOmega(size_t i) - получить угловую скорость i-й колесной пары
  • float getAnalogSignal(int i) - получить аналоговый сигнал с номером i.
  • float *getAnalogSignals() - получить указатель на массив анвлоговых сигналов.
  • void integrationPreStep(state_vector_t &Y, double t) - выполнить метод preStep() для ПЕ.
  • void integrationStep(state_vector_t &Y, double t, double dt) - выполнить метод step() для ПЕ.
  • void integrationPostStep(state_vector_t &Y, double t) - выполнить метод postStep() для ПЕ.
  • void getBrakepipeBeginPressure() - получить давление в начале тормозной магистрали.
  • void getBrakepipeAuxRate() - получить темп дополнительной разрядки ТМ.
  • void setBrakepipePressure() - задать давление в тормозной магистрали.
  • QString getDebugMsg() - получить отладочную строку.
  • void setUks(double value) - задать напряжение контактной сети.
  • void setCurrentKind(int value) - задать род тока в контактной сети.
  • void setEPTControl(size_t i, double value) - задать состояние линии управления ЭПТ.
  • void getEPTCurrent(size_t i) - получить ток в линии управления ЭПТ.
  • void getEPTControl(size_t i) - получить состояние линии управления ЭПТ.
Из этого краткого описания следует, что перечисленные методы предназначены для выполнения трех функций: передать в модуль ПЕ требуемые параметры; получить от модуля ПЕ нужные движку параметры; заставить модуль ПЕ выполнить нужные движку действия. Это своего рода рычаги, с помощью котрых движок игры управляет тем кодом, что разработчик дополнения написал. И при этом симулятор совершенно не интересует, что конкретно происходит внутри этой DLL.

6. Публичные слоты

Слоты, это методы класса QObject и его наследников, которые, будучи связанными с сигналами другого класса, будут выполнятся, когда другой класс вызовет соотвествующий сигнал. Это основная парадигма Qt, которой RRS следует, когда ему это выгодно ;-)

Итак
  • void receiveData(QByteArray data) - прием данных от системы визуализации, а именно состояния клавиатуры. Состояние клавиатуры, путем вызова соответсвующего сигнала рассылается всем ПЕ в поезде и делает это класс Train.
  • void getControlSignals(control_signals_t control_signals) - получение состояния органов управления от внешнего пульта. Срабатывание этого слота приводит к обновлению информации о состоянии пульта, подключенного к симулятору.
Ок, мы разобрали класс Vehicle по полочкам. Для чего я это пишу?

Во-первых, это документация. Её черновой вариант. Но документация как и все что делается в рамках данного проекта - открытое публичное достояние, подлежащее критике и обсуждению. И где, как не на форум проекта, её публиковать и обсуждать?

Во-вторых, такое описание, я надеюсь, поможет понять механику игры и облегчить жизнь разработчикам дополнений. Согласитесь, если вы знаете что творится в кишках программы, под которую вы создаете плагины, то процесс поедет и быстрее, и качественне. Доступ к исходному коду открыт, но данная документация должна послужить путеводителем по этим исходникам.

В-третьих, в нашем сообществе появляются энтузиасты, предлагающие свежие идеи, работающие на благо проекта. Это заставляет меня, как руководителя проекта, идти навстречу таким людям, и, лучший способ - это написание подобных текстов по коду проекта.

Думаю, что мысль приживется, приглашаю к обсуждению.
Возврата к деспотии Ситхов не будет!

Ответить