17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 08:39:19 +02:00
Files
maszyna/vehicle/graph/blocks/cab.cpp

382 lines
11 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "vehicle/graph/blocks/cab.h"
#include "vehicle/graph/graph_catalog.h"
#include <algorithm>
#include <cmath>
namespace vgraph
{
namespace
{
std::vector<std::string> split_labels(std::string const &Text)
{
std::vector<std::string> result;
if (Text.empty())
{
return result;
}
std::string::size_type start{0};
while (true)
{
auto const separator{Text.find(';', start)};
result.emplace_back(Text.substr(start, separator == std::string::npos ? std::string::npos : separator - start));
if (separator == std::string::npos)
{
break;
}
start = separator + 1;
}
return result;
}
} // namespace
// cab_switch
cab_switch::cab_switch(block_setup const &Setup)
: block(Setup), m_behaviour(static_cast<behaviour_type>(choice(Setup, param::behaviour))),
m_positioncount(std::max(1, static_cast<int>(std::lround(number(Setup, param::positions))))), m_min(number(Setup, param::min)), m_max(number(Setup, param::max)),
m_spring(boolean(Setup, param::spring)), m_labels(split_labels(text(Setup, param::labels))), m_animation(static_cast<animation_type>(choice(Setup, param::animation))),
m_step(number(Setup, param::step)), m_offset(number(Setup, param::offset)), m_smoothing(number(Setup, param::smoothing)), m_submodel(text(Setup, param::submodel)),
m_keyincrease(text(Setup, param::key_inc)), m_keydecrease(text(Setup, param::key_dec)), m_command(text(Setup, param::command)), m_hardwarename(text(Setup, param::hw_name))
{
// the default is given as a value, as the author sees it; the switch starts at the position closest to it
auto const defaultvalue{number(Setup, param::default_value)};
if (m_positioncount > 1 && m_max != m_min)
{
m_defaultposition = clamp_position(static_cast<int>(std::lround((defaultvalue - m_min) / (m_max - m_min) * (m_positioncount - 1))));
}
m_position = m_defaultposition;
}
int cab_switch::clamp_position(int const Position) const
{
return std::clamp(Position, 0, m_positioncount - 1);
}
double cab_switch::value_at(int const Position) const
{
if (m_positioncount < 2)
{
return m_min;
}
return m_min + (m_max - m_min) * clamp_position(Position) / (m_positioncount - 1);
}
std::string cab_switch::label(int const Position) const
{
return (Position >= 0 && Position < static_cast<int>(m_labels.size()) ? m_labels[Position] : std::string{});
}
void cab_switch::update(frame &Frame)
{
set(Frame, port::value, value_at(m_position));
set(Frame, port::index, m_position);
}
void cab_switch::increase()
{
m_position = clamp_position(m_position + 1);
}
void cab_switch::decrease()
{
m_position = clamp_position(m_position - 1);
}
void cab_switch::toggle()
{
switch (m_behaviour)
{
case behaviour_type::monostable:
{
// a momentary switch can't stay toggled; a toggle is treated as pressing it, the release brings it back
press();
break;
}
default:
{
auto const other{m_defaultposition == m_positioncount - 1 ? 0 : m_positioncount - 1};
m_position = (m_position == m_defaultposition ? other : m_defaultposition);
break;
}
}
}
void cab_switch::press()
{
switch (m_behaviour)
{
case behaviour_type::multi:
{
// levers are moved with increase and decrease; pressing them has no meaning
break;
}
case behaviour_type::monostable:
{
m_position = (m_defaultposition == m_positioncount - 1 ? 0 : m_positioncount - 1);
break;
}
default:
{
toggle();
break;
}
}
}
void cab_switch::release()
{
if (m_behaviour == behaviour_type::monostable || (m_behaviour == behaviour_type::multi && m_spring))
{
m_position = m_defaultposition;
}
}
void cab_switch::set_position(int const Position)
{
m_position = clamp_position(Position);
}
void cab_switch::set_value(double const Value)
{
if (m_positioncount < 2 || m_max == m_min)
{
return;
}
set_position(static_cast<int>(std::lround((Value - m_min) / (m_max - m_min) * (m_positioncount - 1))));
}
block_type cab_switch::describe()
{
block_type type;
type.id = "cab.switch";
type.category = "cab";
type.name = "Switch";
type.description = "Switch, button or lever of the cab. Its behaviour and animation are defined here, and its value can be connected to any block.";
type.origin = block_origin::cab;
type.ports = {signal_out("pos", value_type::real), signal_out("index", value_type::integer)};
type.params = {
choice_param("behaviour", {"bistable", "monostable", "multi"}, "bistable"),
number_param("positions", 2.0),
number_param("min", 0.0),
number_param("max", 1.0),
number_param("default", 0.0),
boolean_param("spring", false),
text_param("labels"),
choice_param("animation", {"rotate", "move"}, "rotate"),
number_param("step", 45.0, "deg/mm"),
number_param("offset", 0.0, "deg/mm"),
number_param("smoothing", 0.2, "s"),
text_param("submodel"),
text_param("key_inc"),
text_param("key_dec"),
text_param("command"),
text_param("hw_name"),
};
type.create = make_block<cab_switch>;
return type;
}
// cab_lamp
cab_lamp::cab_lamp(block_setup const &Setup)
: block(Setup), m_supplied(is_connected(Setup, port::supply)), m_blink(boolean(Setup, param::blink)), m_blinkperiod(std::max(0.01, number(Setup, param::blink_period))),
m_minvoltage(number(Setup, param::min_voltage)), m_power(std::max(0.0, number(Setup, param::power))), m_submodel(text(Setup, param::submodel))
{
}
void cab_lamp::update(frame &Frame)
{
// without low voltage simulation, or without the supply connected, the lamp is assumed to be powered
auto const lowvoltage{Frame.low_voltage && m_supplied};
auto const voltage{lowvoltage ? forward_in(Frame, port::supply) : 0.0};
auto const powered{false == lowvoltage || voltage >= m_minvoltage};
auto lit{powered && get_bool(Frame, port::on)};
if (lit && m_blink)
{
// blinking follows the time of the graph, so all lamps of the vehicle blink in step
lit = std::fmod(Frame.time, m_blinkperiod) < 0.5 * m_blinkperiod;
}
m_lit = lit;
if (lowvoltage && m_lit && voltage > 1.0)
{
add_back(Frame, port::supply, m_power / voltage);
}
}
block_type cab_lamp::describe()
{
block_type type;
type.id = "fb.lamp";
type.category = "indicators";
type.name = "Lamp";
type.description = "Indicator lamp. With low voltage simulation enabled and the supply connected, it needs enough voltage to light, and draws current.";
type.origin = block_origin::cab;
type.ports = {signal_in("on", value_type::boolean), physical_in("supply", domain::low_voltage, true)};
type.params = {
choice_param("color", {"green", "yellow", "red", "white", "blue"}, "green"),
boolean_param("blink", false),
number_param("blink_period", 1.0, "s"),
number_param("min_voltage", 70.0, "V"),
number_param("power", 2.0, "W"),
text_param("submodel"),
};
type.create = make_block<cab_lamp>;
return type;
}
// cab_gauge
cab_gauge::cab_gauge(block_setup const &Setup, bool const Twoneedles)
: block(Setup), m_twoneedles(Twoneedles), m_min(number(Setup, param::min)), m_max(number(Setup, param::max)), m_anglemin(number(Setup, param::angle_min)),
m_anglemax(number(Setup, param::angle_max)), m_smoothing(number(Setup, param::smoothing)), m_submodel(text(Setup, param::submodel)),
m_submodel2(Twoneedles ? text(Setup, param::submodel2) : std::string{})
{
m_values[0] = m_values[1] = static_cast<float>(std::min(m_min, m_max));
}
void cab_gauge::update(frame &Frame)
{
// the needle stops at the ends of the scale
auto const low{std::min(m_min, m_max)};
auto const high{std::max(m_min, m_max)};
m_values[0] = static_cast<float>(std::clamp(get(Frame, port::value), low, high));
if (m_twoneedles)
{
m_values[1] = static_cast<float>(std::clamp(get(Frame, port::value2), low, high));
}
}
block_type cab_gauge::describe(bool const Twoneedles)
{
block_type type;
type.category = "indicators";
type.origin = block_origin::cab;
if (Twoneedles)
{
type.id = "fb.manometer";
type.name = "Two needle gauge";
type.description = "Gauge with two needles, e.g. pressure gauge of the brake pipe (black) and the main reservoir (red).";
type.ports = {signal_in("black", value_type::real, 0.0, "bar"), signal_in("red", value_type::real, 0.0, "bar", true)};
}
else
{
type.id = "fb.gauge";
type.name = "Gauge";
type.description = "Needle gauge: voltmeter, ammeter, speedometer.";
type.ports = {signal_in("value", value_type::real)};
}
type.params = {
number_param("min", 0.0),
number_param("max", Twoneedles ? 10.0 : 100.0),
text_param("unit", Twoneedles ? "bar" : ""),
number_param("angle_min", -135.0, "deg"),
number_param("angle_max", 135.0, "deg"),
number_param("smoothing", 0.3, "s"),
text_param("submodel"),
};
if (Twoneedles)
{
type.params.push_back(text_param("submodel2"));
}
if (Twoneedles)
{
type.create = [](block_setup const &Setup) -> std::unique_ptr<block> { return std::make_unique<cab_gauge>(Setup, true); };
}
else
{
type.create = [](block_setup const &Setup) -> std::unique_ptr<block> { return std::make_unique<cab_gauge>(Setup, false); };
}
return type;
}
// vehicle_sound
vehicle_sound::vehicle_sound(block_setup const &Setup)
: block(Setup), m_file(text(Setup, param::file)), m_basevolume(number(Setup, param::volume)), m_basepitch(number(Setup, param::pitch)), m_loop(boolean(Setup, param::loop)),
m_placement(static_cast<placement_type>(choice(Setup, param::placement))), m_range(number(Setup, param::range)),
m_offset{number(Setup, param::offset_x), number(Setup, param::offset_y), number(Setup, param::offset_z)}
{
}
void vehicle_sound::update(frame &Frame)
{
m_playing = get_bool(Frame, port::play);
m_volume = static_cast<float>(std::max(0.0, m_basevolume * get(Frame, port::volume)));
m_pitch = static_cast<float>(std::max(0.0, m_basepitch * get(Frame, port::pitch)));
}
char const *vehicle_sound::state_name(std::size_t const Index) const
{
switch (Index)
{
case 0:
return "playing";
case 1:
return "volume";
default:
return "pitch";
}
}
double vehicle_sound::state_value(std::size_t const Index) const
{
switch (Index)
{
case 0:
return m_playing ? 1.0 : 0.0;
case 1:
return m_volume;
default:
return m_pitch;
}
}
block_type vehicle_sound::describe()
{
block_type type;
type.id = "fb.sound";
type.category = "indicators";
type.name = "Sound";
type.description = "Sound of the vehicle or of its cab. Volume and pitch are the parameters multiplied by the inputs; looped sounds play while the play input is on.";
type.origin = block_origin::cab;
type.ports = {signal_in("play", value_type::boolean), signal_in("volume", value_type::real, 1.0, "", true), signal_in("pitch", value_type::real, 1.0, "", true)};
type.params = {
text_param("file"),
number_param("volume", 1.0),
number_param("pitch", 1.0),
boolean_param("loop", true),
choice_param("placement", {"general", "internal", "engine", "external", "external_ambient"}, "internal"),
number_param("range", -1.0, "m"),
number_param("offset_x", 0.0, "m"),
number_param("offset_y", 0.0, "m"),
number_param("offset_z", 0.0, "m"),
};
type.create = make_block<vehicle_sound>;
return type;
}
void register_cab_blocks(catalog &Catalog)
{
Catalog.add(cab_switch::describe());
Catalog.add(cab_lamp::describe());
Catalog.add(cab_gauge::describe(false));
Catalog.add(cab_gauge::describe(true));
Catalog.add(vehicle_sound::describe());
}
} // namespace vgraph