mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-06 04:38:37 +01:00
Add vehicle graph core: .vhg loader, logic and cab blocks, -graph-run and -graph-catalog
This commit is contained in:
381
vehicle/graph/blocks/cab.cpp
Normal file
381
vehicle/graph/blocks/cab.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
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
|
||||
441
vehicle/graph/blocks/cab.h
Normal file
441
vehicle/graph/blocks/cab.h
Normal file
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_block.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// cab controls and indicators. the blocks hold the logic and the values; the cab (TTrain) creates the animated
|
||||
// submodels from their parameters and binds them to the values these blocks expose
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
// switch, button or lever of the cab. its behaviour and animation are defined in the graph, and its value
|
||||
// is available to the other blocks
|
||||
class cab_switch final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
value, // "pos": value of the current position, between min and max
|
||||
index // number of the current position, from 0
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
behaviour,
|
||||
positions,
|
||||
min,
|
||||
max,
|
||||
default_value,
|
||||
spring,
|
||||
labels,
|
||||
animation,
|
||||
step,
|
||||
offset,
|
||||
smoothing,
|
||||
submodel,
|
||||
key_inc,
|
||||
key_dec,
|
||||
command,
|
||||
hw_name
|
||||
};
|
||||
};
|
||||
enum class behaviour_type
|
||||
{
|
||||
bistable, // stays where it was put
|
||||
monostable, // returns to the default position when released, e.g. a push button
|
||||
multi // several positions; with the spring enabled it returns to the default position when released
|
||||
};
|
||||
enum class animation_type
|
||||
{
|
||||
rotate,
|
||||
move
|
||||
};
|
||||
|
||||
explicit cab_switch(block_setup const &Setup);
|
||||
|
||||
void update(frame &Frame) override;
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "position";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
// operations of the player, AI or hardware. they take effect in the next update
|
||||
void increase();
|
||||
void decrease();
|
||||
void toggle();
|
||||
void press();
|
||||
void release();
|
||||
void set_position(int Position);
|
||||
// moves to the position with the value closest to the specified one, e.g. for hardware levers
|
||||
void set_value(double Value);
|
||||
|
||||
int position() const
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
int position_count() const
|
||||
{
|
||||
return m_positioncount;
|
||||
}
|
||||
int default_position() const
|
||||
{
|
||||
return m_defaultposition;
|
||||
}
|
||||
double value_at(int Position) const;
|
||||
behaviour_type behaviour() const
|
||||
{
|
||||
return m_behaviour;
|
||||
}
|
||||
bool spring() const
|
||||
{
|
||||
return m_spring;
|
||||
}
|
||||
// description of the specified position, from the labels parameter; empty if not specified
|
||||
std::string label(int Position) const;
|
||||
// animation of the submodel: offset + step * position, in degrees or millimetres
|
||||
animation_type animation() const
|
||||
{
|
||||
return m_animation;
|
||||
}
|
||||
double step() const
|
||||
{
|
||||
return m_step;
|
||||
}
|
||||
double offset() const
|
||||
{
|
||||
return m_offset;
|
||||
}
|
||||
double smoothing() const
|
||||
{
|
||||
return m_smoothing;
|
||||
}
|
||||
std::string const &submodel() const
|
||||
{
|
||||
return m_submodel;
|
||||
}
|
||||
// bindings: keys, existing simulator command, name for the hardware desk
|
||||
std::string const &key_increase() const
|
||||
{
|
||||
return m_keyincrease;
|
||||
}
|
||||
std::string const &key_decrease() const
|
||||
{
|
||||
return m_keydecrease;
|
||||
}
|
||||
std::string const &command() const
|
||||
{
|
||||
return m_command;
|
||||
}
|
||||
std::string const &hardware_name() const
|
||||
{
|
||||
return m_hardwarename;
|
||||
}
|
||||
|
||||
static block_type describe();
|
||||
|
||||
private:
|
||||
int clamp_position(int Position) const;
|
||||
|
||||
behaviour_type const m_behaviour;
|
||||
int const m_positioncount;
|
||||
double const m_min;
|
||||
double const m_max;
|
||||
int m_defaultposition{0};
|
||||
bool const m_spring;
|
||||
std::vector<std::string> m_labels;
|
||||
animation_type const m_animation;
|
||||
double const m_step;
|
||||
double const m_offset;
|
||||
double const m_smoothing;
|
||||
std::string const m_submodel;
|
||||
std::string const m_keyincrease;
|
||||
std::string const m_keydecrease;
|
||||
std::string const m_command;
|
||||
std::string const m_hardwarename;
|
||||
int m_position{0};
|
||||
};
|
||||
|
||||
// indicator lamp. when the graph simulates low voltage circuits and the supply input is connected, the lamp lights
|
||||
// only with enough voltage, and draws current from the supply
|
||||
class cab_lamp final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
on,
|
||||
supply
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
color,
|
||||
blink,
|
||||
blink_period,
|
||||
min_voltage,
|
||||
power,
|
||||
submodel
|
||||
};
|
||||
};
|
||||
|
||||
explicit cab_lamp(block_setup const &Setup);
|
||||
|
||||
void update(frame &Frame) override;
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "lit";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_lit ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
// stable address of the state, for TButton::AssignBool
|
||||
bool const *lit_state() const
|
||||
{
|
||||
return &m_lit;
|
||||
}
|
||||
std::string const &submodel() const
|
||||
{
|
||||
return m_submodel;
|
||||
}
|
||||
|
||||
static block_type describe();
|
||||
|
||||
private:
|
||||
bool const m_supplied; // whether the supply input is connected
|
||||
bool const m_blink;
|
||||
double const m_blinkperiod;
|
||||
double const m_minvoltage;
|
||||
double const m_power;
|
||||
std::string const m_submodel;
|
||||
bool m_lit{false};
|
||||
};
|
||||
|
||||
// needle gauge with one or two needles: voltmeter, ammeter, speedometer, pressure gauge
|
||||
class cab_gauge final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
value,
|
||||
value2
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
min,
|
||||
max,
|
||||
unit,
|
||||
angle_min,
|
||||
angle_max,
|
||||
smoothing,
|
||||
submodel,
|
||||
submodel2
|
||||
};
|
||||
};
|
||||
|
||||
cab_gauge(block_setup const &Setup, bool Twoneedles);
|
||||
|
||||
void update(frame &Frame) override;
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return m_twoneedles ? 2 : 1;
|
||||
}
|
||||
char const *state_name(std::size_t Index) const override
|
||||
{
|
||||
return Index == 0 ? "value" : "value2";
|
||||
}
|
||||
double state_value(std::size_t Index) const override
|
||||
{
|
||||
return m_values[Index];
|
||||
}
|
||||
|
||||
// stable addresses of the values shown by the needles, clamped to the scale, for TGauge::AssignFloat
|
||||
float *value_state(std::size_t Needle)
|
||||
{
|
||||
return &m_values[Needle];
|
||||
}
|
||||
bool two_needles() const
|
||||
{
|
||||
return m_twoneedles;
|
||||
}
|
||||
double min() const
|
||||
{
|
||||
return m_min;
|
||||
}
|
||||
double max() const
|
||||
{
|
||||
return m_max;
|
||||
}
|
||||
double angle_min() const
|
||||
{
|
||||
return m_anglemin;
|
||||
}
|
||||
double angle_max() const
|
||||
{
|
||||
return m_anglemax;
|
||||
}
|
||||
double smoothing() const
|
||||
{
|
||||
return m_smoothing;
|
||||
}
|
||||
std::string const &submodel(std::size_t Needle) const
|
||||
{
|
||||
return Needle == 0 ? m_submodel : m_submodel2;
|
||||
}
|
||||
|
||||
static block_type describe(bool Twoneedles);
|
||||
|
||||
private:
|
||||
bool const m_twoneedles;
|
||||
double const m_min;
|
||||
double const m_max;
|
||||
double const m_anglemin;
|
||||
double const m_anglemax;
|
||||
double const m_smoothing;
|
||||
std::string const m_submodel;
|
||||
std::string const m_submodel2;
|
||||
float m_values[2]{0.f, 0.f};
|
||||
};
|
||||
|
||||
// sound of the vehicle or of its cab. sounds of a graph vehicle are defined in the graph instead of the .mmd file
|
||||
class vehicle_sound final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
play,
|
||||
volume,
|
||||
pitch
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
file,
|
||||
volume,
|
||||
pitch,
|
||||
loop,
|
||||
placement,
|
||||
range,
|
||||
offset_x,
|
||||
offset_y,
|
||||
offset_z
|
||||
};
|
||||
};
|
||||
// the same placements as sound_placement of the audio subsystem
|
||||
enum class placement_type
|
||||
{
|
||||
general,
|
||||
internal,
|
||||
engine,
|
||||
external,
|
||||
external_ambient
|
||||
};
|
||||
|
||||
explicit vehicle_sound(block_setup const &Setup);
|
||||
|
||||
void update(frame &Frame) override;
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
char const *state_name(std::size_t Index) const override;
|
||||
double state_value(std::size_t Index) const override;
|
||||
|
||||
bool playing() const
|
||||
{
|
||||
return m_playing;
|
||||
}
|
||||
// volume and pitch: parameter multiplied by the input
|
||||
float volume() const
|
||||
{
|
||||
return m_volume;
|
||||
}
|
||||
float pitch() const
|
||||
{
|
||||
return m_pitch;
|
||||
}
|
||||
std::string const &file() const
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
bool loop() const
|
||||
{
|
||||
return m_loop;
|
||||
}
|
||||
placement_type placement() const
|
||||
{
|
||||
return m_placement;
|
||||
}
|
||||
double range() const
|
||||
{
|
||||
return m_range;
|
||||
}
|
||||
double offset_x() const
|
||||
{
|
||||
return m_offset[0];
|
||||
}
|
||||
double offset_y() const
|
||||
{
|
||||
return m_offset[1];
|
||||
}
|
||||
double offset_z() const
|
||||
{
|
||||
return m_offset[2];
|
||||
}
|
||||
|
||||
static block_type describe();
|
||||
|
||||
private:
|
||||
std::string const m_file;
|
||||
double const m_basevolume;
|
||||
double const m_basepitch;
|
||||
bool const m_loop;
|
||||
placement_type const m_placement;
|
||||
double const m_range;
|
||||
double const m_offset[3];
|
||||
bool m_playing{false};
|
||||
float m_volume{0.f};
|
||||
float m_pitch{1.f};
|
||||
};
|
||||
|
||||
} // namespace vgraph
|
||||
625
vehicle/graph/blocks/logic.cpp
Normal file
625
vehicle/graph/blocks/logic.cpp
Normal file
@@ -0,0 +1,625 @@
|
||||
/*
|
||||
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/graph_catalog.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
// generic logic and arithmetic blocks. boolean signals are stored as 0 and 1, and read as true above 0.5
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// tolerance of the equality comparisons; switch positions and constants are compared, not results of long computations
|
||||
double constexpr equality_tolerance{1e-6};
|
||||
|
||||
block_type make_type(std::string Id, std::string Name, std::string Description)
|
||||
{
|
||||
block_type type;
|
||||
type.id = std::move(Id);
|
||||
type.category = "logic";
|
||||
type.name = std::move(Name);
|
||||
type.description = std::move(Description);
|
||||
type.origin = block_origin::primitive;
|
||||
return type;
|
||||
}
|
||||
|
||||
class logic_compare final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
a,
|
||||
q
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
op,
|
||||
value
|
||||
};
|
||||
};
|
||||
enum class operation
|
||||
{
|
||||
ne,
|
||||
eq,
|
||||
lt,
|
||||
le,
|
||||
gt,
|
||||
ge
|
||||
};
|
||||
|
||||
explicit logic_compare(block_setup const &Setup) : block(Setup), m_operation(static_cast<operation>(choice(Setup, param::op))), m_value(number(Setup, param::value)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const a{get(Frame, port::a)};
|
||||
auto result{false};
|
||||
switch (m_operation)
|
||||
{
|
||||
case operation::ne:
|
||||
result = std::abs(a - m_value) > equality_tolerance;
|
||||
break;
|
||||
case operation::eq:
|
||||
result = std::abs(a - m_value) <= equality_tolerance;
|
||||
break;
|
||||
case operation::lt:
|
||||
result = a < m_value;
|
||||
break;
|
||||
case operation::le:
|
||||
result = a <= m_value + equality_tolerance;
|
||||
break;
|
||||
case operation::gt:
|
||||
result = a > m_value;
|
||||
break;
|
||||
case operation::ge:
|
||||
result = a >= m_value - equality_tolerance;
|
||||
break;
|
||||
}
|
||||
set_bool(Frame, port::q, result);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("logic.compare", "Compare", "Compares the input with a constant value.")};
|
||||
type.ports = {signal_in("a", value_type::real), signal_out("q", value_type::boolean)};
|
||||
type.params = {choice_param("op", {"ne", "eq", "lt", "le", "gt", "ge"}, "ne"), number_param("value", 0.0)};
|
||||
type.create = make_block<logic_compare>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
operation const m_operation;
|
||||
double const m_value;
|
||||
};
|
||||
|
||||
enum class gate
|
||||
{
|
||||
and_,
|
||||
or_,
|
||||
xor_,
|
||||
nand_,
|
||||
nor_
|
||||
};
|
||||
|
||||
class logic_gate final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
a,
|
||||
b,
|
||||
q
|
||||
};
|
||||
};
|
||||
|
||||
logic_gate(block_setup const &Setup, gate const Gate) : block(Setup), m_gate(Gate) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const a{get_bool(Frame, port::a)};
|
||||
auto const b{get_bool(Frame, port::b)};
|
||||
auto result{false};
|
||||
switch (m_gate)
|
||||
{
|
||||
case gate::and_:
|
||||
result = a && b;
|
||||
break;
|
||||
case gate::or_:
|
||||
result = a || b;
|
||||
break;
|
||||
case gate::xor_:
|
||||
result = a != b;
|
||||
break;
|
||||
case gate::nand_:
|
||||
result = false == (a && b);
|
||||
break;
|
||||
case gate::nor_:
|
||||
result = false == (a || b);
|
||||
break;
|
||||
}
|
||||
set_bool(Frame, port::q, result);
|
||||
}
|
||||
|
||||
template <gate Gate_> static std::unique_ptr<block> create(block_setup const &Setup)
|
||||
{
|
||||
return std::make_unique<logic_gate>(Setup, Gate_);
|
||||
}
|
||||
|
||||
template <gate Gate_> static block_type describe(std::string Id, std::string Name, double const Default)
|
||||
{
|
||||
// unconnected inputs default to the neutral value of the gate, so a gate can be used with one input
|
||||
auto type{make_type(std::move(Id), std::move(Name), "Logic gate.")};
|
||||
type.ports = {signal_in("a", value_type::boolean, Default), signal_in("b", value_type::boolean, Default, "", true), signal_out("q", value_type::boolean)};
|
||||
type.create = create<Gate_>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
gate const m_gate;
|
||||
};
|
||||
|
||||
class logic_not final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
a,
|
||||
q
|
||||
};
|
||||
};
|
||||
|
||||
explicit logic_not(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
set_bool(Frame, port::q, false == get_bool(Frame, port::a));
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("logic.not", "NOT", "Negation.")};
|
||||
type.ports = {signal_in("a", value_type::boolean), signal_out("q", value_type::boolean)};
|
||||
type.create = make_block<logic_not>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
// set-reset latch. it's deferred, so it can close loops, e.g. self-holding contactor circuits
|
||||
class logic_sr final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
s,
|
||||
r,
|
||||
q
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
reset_priority,
|
||||
initial
|
||||
};
|
||||
};
|
||||
|
||||
explicit logic_sr(block_setup const &Setup) : block(Setup), m_resetpriority(boolean(Setup, param::reset_priority)), m_state(boolean(Setup, param::initial)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const s{get_bool(Frame, port::s)};
|
||||
auto const r{get_bool(Frame, port::r)};
|
||||
if (s && r)
|
||||
{
|
||||
m_state = (false == m_resetpriority);
|
||||
}
|
||||
else if (s)
|
||||
{
|
||||
m_state = true;
|
||||
}
|
||||
else if (r)
|
||||
{
|
||||
m_state = false;
|
||||
}
|
||||
}
|
||||
|
||||
void commit(frame &Frame) override
|
||||
{
|
||||
set_bool(Frame, port::q, m_state);
|
||||
}
|
||||
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "state";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_state ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("logic.sr", "SR latch", "Set sets the output, reset clears it. The output changes in the next step, so the block can close loops.")};
|
||||
type.deferred = true;
|
||||
type.ports = {signal_in("s", value_type::boolean), signal_in("r", value_type::boolean, 0.0, "", true), signal_out("q", value_type::boolean)};
|
||||
type.params = {boolean_param("reset_priority", true), boolean_param("initial", false)};
|
||||
type.create = make_block<logic_sr>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_resetpriority;
|
||||
bool m_state;
|
||||
};
|
||||
|
||||
// on-delay (TON) and off-delay (TOF) timers
|
||||
class logic_timer final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
a,
|
||||
q
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
time
|
||||
};
|
||||
};
|
||||
|
||||
logic_timer(block_setup const &Setup, bool const Ondelay) : block(Setup), m_ondelay(Ondelay), m_delay(std::max(0.0, number(Setup, param::time))) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const a{get_bool(Frame, port::a)};
|
||||
if (a == m_ondelay)
|
||||
{
|
||||
m_elapsed = std::min(m_elapsed + Frame.dt, m_delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_elapsed = 0.0;
|
||||
}
|
||||
auto const expired{m_elapsed >= m_delay};
|
||||
m_output = (m_ondelay ? (a && expired) : (a || false == expired));
|
||||
}
|
||||
|
||||
void commit(frame &Frame) override
|
||||
{
|
||||
set_bool(Frame, port::q, m_output);
|
||||
}
|
||||
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "elapsed";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_elapsed;
|
||||
}
|
||||
|
||||
template <bool Ondelay_> static std::unique_ptr<block> create(block_setup const &Setup)
|
||||
{
|
||||
return std::make_unique<logic_timer>(Setup, Ondelay_);
|
||||
}
|
||||
|
||||
template <bool Ondelay_> static block_type describe()
|
||||
{
|
||||
auto type{Ondelay_ ? make_type("logic.ton", "On delay", "The output follows the input switching on after the specified time, and switching off immediately.")
|
||||
: make_type("logic.tof", "Off delay", "The output follows the input switching on immediately, and switching off after the specified time.")};
|
||||
type.deferred = true;
|
||||
type.ports = {signal_in("a", value_type::boolean), signal_out("q", value_type::boolean)};
|
||||
type.params = {number_param("time", 1.0, "s")};
|
||||
type.create = create<Ondelay_>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_ondelay;
|
||||
double const m_delay;
|
||||
// for the on-delay: time the input is on; for the off-delay: time the input is off
|
||||
// (both start expired for the off-delay, so the output is off until the input is switched on)
|
||||
double m_elapsed{m_ondelay ? 0.0 : m_delay};
|
||||
bool m_output{false};
|
||||
};
|
||||
|
||||
// delays the signal by one step; the explicit way to close a loop
|
||||
class logic_delay final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
a,
|
||||
q
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
initial
|
||||
};
|
||||
};
|
||||
|
||||
explicit logic_delay(block_setup const &Setup) : block(Setup), m_value(number(Setup, param::initial)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
m_value = get(Frame, port::a);
|
||||
}
|
||||
|
||||
void commit(frame &Frame) override
|
||||
{
|
||||
set(Frame, port::q, m_value);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("logic.delay", "Unit delay", "Passes the input to the output one step later. Use it to close loops.")};
|
||||
type.deferred = true;
|
||||
type.ports = {signal_in("a", value_type::real), signal_out("q", value_type::real)};
|
||||
type.params = {number_param("initial", 0.0)};
|
||||
type.create = make_block<logic_delay>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double m_value;
|
||||
};
|
||||
|
||||
class logic_hysteresis final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
a,
|
||||
q
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
lo,
|
||||
hi,
|
||||
initial
|
||||
};
|
||||
};
|
||||
|
||||
explicit logic_hysteresis(block_setup const &Setup)
|
||||
: block(Setup), m_low(number(Setup, param::lo)), m_high(number(Setup, param::hi)), m_state(boolean(Setup, param::initial))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const a{get(Frame, port::a)};
|
||||
if (a >= m_high)
|
||||
{
|
||||
m_state = true;
|
||||
}
|
||||
else if (a <= m_low)
|
||||
{
|
||||
m_state = false;
|
||||
}
|
||||
set_bool(Frame, port::q, m_state);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("logic.hyst", "Hysteresis", "Switches on when the input reaches the upper threshold, and off when it drops to the lower one.")};
|
||||
type.ports = {signal_in("a", value_type::real), signal_out("q", value_type::boolean)};
|
||||
type.params = {number_param("lo", 0.0), number_param("hi", 1.0), boolean_param("initial", false)};
|
||||
type.create = make_block<logic_hysteresis>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_low;
|
||||
double const m_high;
|
||||
bool m_state;
|
||||
};
|
||||
|
||||
class logic_const final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
q
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
value
|
||||
};
|
||||
};
|
||||
|
||||
explicit logic_const(block_setup const &Setup) : block(Setup), m_value(number(Setup, param::value)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
set(Frame, port::q, m_value);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("logic.const", "Constant", "Constant value.")};
|
||||
type.ports = {signal_out("q", value_type::real)};
|
||||
type.params = {number_param("value", 1.0)};
|
||||
type.create = make_block<logic_const>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_value;
|
||||
};
|
||||
|
||||
class logic_math final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
a,
|
||||
b,
|
||||
q
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
op
|
||||
};
|
||||
};
|
||||
enum class operation
|
||||
{
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
min,
|
||||
max
|
||||
};
|
||||
|
||||
explicit logic_math(block_setup const &Setup) : block(Setup), m_operation(static_cast<operation>(choice(Setup, param::op))) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const a{get(Frame, port::a)};
|
||||
auto const b{get(Frame, port::b)};
|
||||
auto result{0.0};
|
||||
switch (m_operation)
|
||||
{
|
||||
case operation::add:
|
||||
result = a + b;
|
||||
break;
|
||||
case operation::sub:
|
||||
result = a - b;
|
||||
break;
|
||||
case operation::mul:
|
||||
result = a * b;
|
||||
break;
|
||||
case operation::div:
|
||||
// division by zero gives zero rather than infinity, which would spread through the whole graph
|
||||
result = (b != 0.0 ? a / b : 0.0);
|
||||
break;
|
||||
case operation::min:
|
||||
result = std::min(a, b);
|
||||
break;
|
||||
case operation::max:
|
||||
result = std::max(a, b);
|
||||
break;
|
||||
}
|
||||
set(Frame, port::q, result);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("logic.math", "Arithmetic", "Arithmetic operation on two inputs. Division by zero gives zero.")};
|
||||
type.ports = {signal_in("a", value_type::real), signal_in("b", value_type::real, 1.0), signal_out("q", value_type::real)};
|
||||
type.params = {choice_param("op", {"add", "sub", "mul", "div", "min", "max"}, "mul")};
|
||||
type.create = make_block<logic_math>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
operation const m_operation;
|
||||
};
|
||||
|
||||
class logic_select final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
sel,
|
||||
a,
|
||||
b,
|
||||
q
|
||||
};
|
||||
};
|
||||
|
||||
explicit logic_select(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
set(Frame, port::q, get_bool(Frame, port::sel) ? get(Frame, port::b) : get(Frame, port::a));
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("logic.select", "Select", "Passes input a when sel is off, and input b when sel is on.")};
|
||||
type.ports = {signal_in("sel", value_type::boolean), signal_in("a", value_type::real), signal_in("b", value_type::real), signal_out("q", value_type::real)};
|
||||
type.create = make_block<logic_select>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_logic_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(logic_compare::describe());
|
||||
Catalog.add(logic_gate::describe<gate::and_>("logic.and", "AND", 1.0));
|
||||
Catalog.add(logic_gate::describe<gate::or_>("logic.or", "OR", 0.0));
|
||||
Catalog.add(logic_gate::describe<gate::xor_>("logic.xor", "XOR", 0.0));
|
||||
Catalog.add(logic_gate::describe<gate::nand_>("logic.nand", "NAND", 1.0));
|
||||
Catalog.add(logic_gate::describe<gate::nor_>("logic.nor", "NOR", 0.0));
|
||||
Catalog.add(logic_not::describe());
|
||||
Catalog.add(logic_sr::describe());
|
||||
Catalog.add(logic_timer::describe<true>());
|
||||
Catalog.add(logic_timer::describe<false>());
|
||||
Catalog.add(logic_delay::describe());
|
||||
Catalog.add(logic_hysteresis::describe());
|
||||
Catalog.add(logic_const::describe());
|
||||
Catalog.add(logic_math::describe());
|
||||
Catalog.add(logic_select::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
Reference in New Issue
Block a user