-
Notifications
You must be signed in to change notification settings - Fork 5
Tutorial ‐ Interactive scripting (Cpp)
This is short tutorial on scripting functionality for interactive controls in C++.
It is build on the original DA40-NG aircraft ( Tutorial 2 ‐ Aircraft (Cpp) ), we recommed to review that one first.
You can find more info on interactive controls on Outerra wiki in part Interactive .
Declare action variables, which will be used to store the id of action handlers
#include "ot/aircraft_script.h"
const double PI = 3.14159265358979323846;
struct bones
{
static int propeller;
static int wheel_front;
static int wheel_right;
static int wheel_left;
static int elevator_right;
static int elevator_left;
static int rudder;
static int aileron_right;
static int aileron_left;
static int flap_right;
static int flap_left;
static int rudder_pedal_right;
static int rudder_pedal_left;
static int brake_pedal_left;
static int brake_pedal_right;
static int throttle_handle;
};
struct meshes
{
static int prop_blur;
static int blade_one;
static int blade_two;
static int blade_three;
};
struct sounds
{
static int rumble;
static int eng_exterior;
static int eng_interior;
static int prop_exterior;
static int prop_interior;
};
struct sources
{
static int rumble_exterior;
static int rumble_interior;
static int eng_exterior;
static int eng_interior;
static int prop_exterior;
static int prop_interior;
};
// Declare action variables
struct actions
{
static int act_throttle_lever;
static int act_rudder_pedal_L;
static int act_rudder_pedal_R;
static int act_brake_pedal_L;
static int act_brake_pedal_R;
};
Add functions for handling actions
class tutorial_aircraft_interactive_plugin : public ot::aircraft_script
{
ot::chassis_params init_chassis(const coid::charstr& params) override;
void initialize(bool reload) override;
void update_frame(float dt) override;
float clamp(float val, float minval, float maxval);
void landing_lights(float val, uint code, uint channel, int handler_id);
void navigation_lights(float val, uint code, uint channel, int handler_id);
void engine(int flags, uint code, uint channel, int handler_id);
void brakes(float val, uint code, uint channel, int handler_id);
void ailerons(float val, uint code, uint channel, int handler_id);
void elevator(float val, uint code, uint channel, int handler_id);
// Throttle action(keyboard inputs 'PgUp' and 'PgDn')
void throttle(float val, uint code, uint channel, int handler_id);
// Rudder action (keyboard inputs 'Z' and 'X')
void rudder(float val, uint code, uint channel, int handler_id);
// Knobs
// Throttle lever interactive knob
void throttle_knob(float val, uint code, uint channel, int handler_id);
// Rudder left pedal interactive knob
void rudder_knob_L(float val, uint code, uint channel, int handler_id);
// Rudder right pedal interactive knob
void rudder_knob_R(float val, uint code, uint channel, int handler_id);
// Brake left pedal interactive knob
void brake_knob_L(float val, uint code, uint channel, int handler_id);
// Brake right pedal interactive knob
void brake_knob_R(float val, uint code, uint channel, int handler_id);
bool started;
float braking;
uint nav_light_offset;
iref <ot::jsb> jsbsim = nullptr;
iref<ot::geomob> geom = nullptr;
iref<ot::sndgrp> snd = nullptr;
};
Define static members
#pragma once
#include "tutorial_aircraft_interactive_plugin.hpp"
int bones::propeller = -1;
int bones::wheel_front = -1;
int bones::wheel_right = -1;
int bones::wheel_left = -1;
int bones::elevator_right = -1;
int bones::elevator_left = -1;
int bones::rudder = -1;
int bones::aileron_right = -1;
int bones::aileron_left = -1;
int bones::flap_right = -1;
int bones::flap_left = -1;
int bones::rudder_pedal_right = -1;
int bones::rudder_pedal_left = -1;
int bones::brake_pedal_left = -1;
int bones::brake_pedal_right = -1;
int bones::throttle_handle = -1;
int meshes::prop_blur = -1;
int meshes::blade_one = -1;
int meshes::blade_two = -1;
int meshes::blade_three = -1;
int sounds::rumble = -1;
int sounds::eng_exterior = -1;
int sounds::eng_interior = -1;
int sounds::prop_exterior = -1;
int sounds::prop_interior = -1;
int sources::rumble_exterior = -1;
int sources::rumble_interior = -1;
int sources::eng_exterior = -1;
int sources::eng_interior = -1;
int sources::prop_exterior = -1;
int sources::prop_interior = -1;
// Define static members (to -1 for debug purpose)
int actions::act_throttle_lever = -1;
int actions::act_rudder_pedal_L = -1;
int actions::act_rudder_pedal_R = -1;
int actions::act_brake_pedal_L = -1;
int actions::act_brake_pedal_R = -1;
Modify existing action handling functions
namespace
{
IFC_REGISTER_CLIENT(tutorial_aircraft_interactive_plugin);
};
float tutorial_aircraft_interactive_plugin::clamp(float val, float minval, float maxval)
{
if (val < minval)
{
return minval;
}
else if (val > maxval)
{
return maxval;
}
else
{
return val;
}
}
void tutorial_aircraft_interactive_plugin::engine(int flags, uint code, uint channel, int handler_id)
{
if (!this->jsbsim)
{
return;
}
this->started ^= 1;
if (this->started == 1)
{
this->jsbsim->operator()("propulsion/starter_cmd", 1);
this->jsbsim->operator()("propulsion/magneto_cmd", 1);
}
else
{
this->jsbsim->operator()("propulsion/starter_cmd", 0);
this->jsbsim->operator()("propulsion/magneto_cmd", 0);
}
}
void tutorial_aircraft_interactive_plugin::landing_lights(float val, uint code, uint channel, int handler_id)
{
light_mask(0x3, val > 0);
};
void tutorial_aircraft_interactive_plugin::navigation_lights(float val, uint code, uint channel, int handler_id)
{
light_mask(0x3, val > 0, nav_light_offset);
}
// Aileron action (keyboard input 'A' and 'D'), and also knob ( controlled in interactive mode with "roll_lever")
void tutorial_aircraft_interactive_plugin::ailerons(float val, uint code, uint channel, int handler_id)
{
this->jsbsim->operator()("fcs/aileron-cmd-norm", val);
// Bone animations were moved here from update_frame to avoid bugs (e.g. when the knob is grabbed in interactive mode, it is in conflict with the bone position setting/animating through script)
this->geom->rotate_joint_orig(bones::aileron_right, static_cast<float>(this->jsbsim->operator()("fcs/right-aileron-pos-rad")), { -1, 0, 0 });
this->geom->rotate_joint_orig(bones::aileron_left, static_cast<float>(this->jsbsim->operator()("fcs/left-aileron-pos-rad")), { 1, 0, 0 });
}
// Elevator action (keyboard input 'W' and 'S')
void tutorial_aircraft_interactive_plugin::elevator(float val, uint code, uint channel, int handler_id)
{
this->jsbsim->operator()("fcs/elevator-cmd-norm", -val);
// Bone animations were moved here from update_frame to avoid bugs (e.g. when the knob is grabbed in interactive mode, it is in conflict with the bone position setting/animating through script)
this->geom->rotate_joint_orig(bones::elevator_right, static_cast<float>(this->jsbsim->operator()("fcs/elevator-pos-rad")), { 1, 0, 0 });
this->geom->rotate_joint_orig(bones::elevator_left, static_cast<float>(this->jsbsim->operator()("fcs/elevator-pos-rad")), { 1, 0, 0 });
}
// Brake action (keyboard input 'B')
void tutorial_aircraft_interactive_plugin::brakes(float val, uint code, uint channel, int handler_id)
{
// Previous code for setting properties was moved to the brake pedals knobs, and those knobs are set instead(they then set the properties)
// Set target values of left and right brake pedal actions
this->set_action_value(actions::act_brake_pedal_L, val, false);
this->set_action_value(actions::act_brake_pedal_R, val, false);
}
Define new functions for handling actions
Method "set_action_value" was used in this case, but method "set_instant_action_value" can be also used.
Note: both will rotate/move the interactive elements in the model in game.
Method "set_action_value" is used for setting the target value of the action handler and executing it's code
- 1.param - action id (returned from methods "register_handler", "register_axis", etc., when defining them )
- 2.param - target value, to which the action should go (the value is not set instantly, the value of the action handler changes based on it's own velocity and acceleration, until it reaches the target value )
- 3.param - used in "set_action_value" method, to determine, if the value should be set (if true, centering of the action is disabled)
Funtion "set_instant_action_value" is mainly used for setting the target value of the action handler without executing it's code (when "notify" is false)
- 1.param - action id (returned from methods"register_handler", "register_axis", etc., when defining them )
- 2.param - target value, to which the action should go (the value is not set instantly, the value of the action handler changes based on it's own velocity and acceleration, until it reaches the target value )
- 3.param - used in "set_instant_action_value" to determine, if the action should be invoked or not.
If "invoked" is true, the code in the action handler is executed (same as using "set_action_value")
If "invoked" is false, the action handler's value is changed, without executing it's code (useful when working with interactive knobs, because changing the knob's action value means, that the knob (elements like switch, lever, etc.) in the model will move/rotate)
Note: in this case, the "act_rudder_pedal_R" needs to be set to positive value and "act_rudder_pedal_L" to negative value, so that they will be animated correctly (interactive model elements are animated, when their value changes).
// Throttle action (keyboard inputs 'PgUp' and 'PgDn')
void tutorial_aircraft_interactive_plugin::throttle(float val, uint code, uint channel, int handler_id)
{
this->set_action_value(actions::act_throttle_lever, val, true);
}
// Rudder action (keyboard inputs 'Z' and 'X')
void tutorial_aircraft_interactive_plugin::rudder(float val, uint code, uint channel, int handler_id)
{
this->set_action_value(val > 0 ? actions::act_rudder_pedal_R : actions::act_rudder_pedal_L, val > 0 ? val : -val, false);
}
// Knobs
// Throttle lever interactive knob
void tutorial_aircraft_interactive_plugin::throttle_knob(float val, uint code, uint channel, int handler_id)
{
// Set throttle jsbsim property
this->jsbsim->operator()("fcs/throttle-cmd-norm", val);
}
// Rudder left pedal interactive knob
void tutorial_aircraft_interactive_plugin::rudder_knob_L(float val, uint code, uint channel, int handler_id)
{
// Set rudder jsbsim property
this->jsbsim->operator()("fcs/rudder-cmd-norm", val);
}
// Rudder right pedal interactive knob
void tutorial_aircraft_interactive_plugin::rudder_knob_R(float val, uint code, uint channel, int handler_id)
{
// Set rudder jsbsim property
this->jsbsim->operator()("fcs/rudder-cmd-norm", val);
}
// Brake left pedal interactive knob
void tutorial_aircraft_interactive_plugin::brake_knob_L(float val, uint code, uint channel, int handler_id)
{
// Store braking value
this->braking = val;
// Set left brake jsbsim property
this->jsbsim->operator()("fcs/left-brake-cmd-norm", val);
}
// Brake right pedal interactive knob
void tutorial_aircraft_interactive_plugin::brake_knob_R(float val, uint code, uint channel, int handler_id)
{
// Store braking value
this->braking = val;
// Set right brake jsbsim property
this->jsbsim->operator()("fcs/right-brake-cmd-norm", val);
}
Register handlers
Knobs are called either when the knob is controlled in interactive mode (for example when throttle lever is grabbed and moved), or when their value is changed from script, using method "set_action_value" of "set_instant_action_value".
Note: in this case the pitch/roll handle rotates without the need of scripting (animating through geomob), because it's binded with actions in the model. Similarly, the throttle lever, pedals, and switches in the model will still animate even if not defined in the script, but they won't perform the intended functionality.
If knob does have defined action name, then that name is used as the action name.
Example: this model has interactive knob called "roll_lever", which has action name defined as "air/controls/aileron" (defined action in air.cfg IOMap configuration) assigned to it, therefore it is referenced to as "air/controls/aileron".
If knob does not have defined action name, automatic name is assigned, so that it can be refered by script. Format of automatic generated name is "knob_action_[bone_name]", for example for bone attribute with name (knob name) "pedal_left" it is "knob_action_rudder_pedal_left".
Note: when knob handler's value is changed through script, the knob (interactive element in model) moves to the set position. This way the knobs can be "animated" without using animating functionality from geomob...
ot::chassis_params tutorial_aircraft_interactive_plugin::init_chassis(const coid::charstr& params)
{
bones::propeller = get_joint_id("propel");
bones::wheel_front = get_joint_id("front_wheel");
bones::wheel_right = get_joint_id("right_wheel");
bones::wheel_left = get_joint_id("left_wheel");
bones::elevator_right = get_joint_id("elevator_right");
bones::elevator_left = get_joint_id("elevator_left");
bones::rudder = get_joint_id("rudder");
bones::aileron_right = get_joint_id("aileron_right");
bones::aileron_left = get_joint_id("aileron_left");
bones::flap_right = get_joint_id("flap_right");
bones::flap_left = get_joint_id("flap_left");
bones::rudder_pedal_right = get_joint_id("rudder_pedal_right");
bones::rudder_pedal_left = get_joint_id("rudder_pedal_left");
bones::brake_pedal_left = get_joint_id("brake_pedal_left");
bones::brake_pedal_right = get_joint_id("brake_pedal_right");
bones::throttle_handle = get_joint_id("throttle_lever");
meshes::prop_blur = get_mesh_id("propel_blur");
meshes::blade_one = get_mesh_id("main_blade_01");
meshes::blade_two = get_mesh_id("main_blade_02");
meshes::blade_three = get_mesh_id("main_blade_03");
sounds::rumble = load_sound("sounds/engine/engn1.ogg");
sounds::eng_interior = load_sound("sounds/engine/engn1_inn.ogg");
sounds::prop_interior = load_sound("sounds/engine/prop1_inn.ogg");
sounds::eng_exterior = load_sound("sounds/engine/engn1_out.ogg");
sounds::prop_exterior = load_sound("sounds/engine/prop1_out.ogg");
sources::rumble_interior = add_sound_emitter_id(bones::propeller, -1, 0.5f);
sources::eng_interior = add_sound_emitter_id(bones::propeller, 0, 0.5f);
sources::prop_interior = add_sound_emitter_id(bones::propeller, 0, 0.5f);
sources::rumble_exterior = add_sound_emitter_id(bones::propeller, 1, 3.0f);
sources::eng_exterior = add_sound_emitter_id(bones::propeller, 0, 3.0f);
sources::prop_exterior = add_sound_emitter_id(bones::propeller, 0, 3.0f);
ot::light_params light_parameters = { .size = 0.1f, .angle = 100.f, .edge = 0.25f, .intensity = 5.f, .color = { 1.0f, 1.0f, 1.0f, 0.0f }, .fadeout = 0.05f, };
add_spot_light({ 4.5f, 1.08f, 0.98f }, { -0.1f, 1.f, 0.3f }, light_parameters);
add_spot_light({ -4.5f, 1.08f, 0.98f }, { 0.1f, 1.f, 0.3f }, light_parameters);
light_parameters = { .size = 0.035f, .angle = 100.f, .edge = 1.f, .intensity = 20.f, .color = { 1.0f, 1.0f, 1.0f, 0.0f }, .range = 0.0001f, .fadeout = 0.1f, };
nav_light_offset = add_point_light({ 5.08f, 0.18f, 1.33f }, light_parameters);
light_parameters.color = { 0.f, 1.f, 0.f, 0.f };
add_point_light({ -5.05f, 0.18f, 1.33f }, light_parameters);
register_event_handler("air/engines/on", &tutorial_aircraft_interactive_plugin::engine);
register_axis_handler("air/lights/landing_lights", &tutorial_aircraft_interactive_plugin::landing_lights, { .minval = 0.f, .maxval = 1.f, .cenvel = 0.f, .vel = 10.f });
register_axis_handler("air/lights/nav_lights", &tutorial_aircraft_interactive_plugin::navigation_lights, { .minval = 0.f, .maxval = 1.f, .cenvel = 0.f, .vel = 10.f });
register_axis_handler("air/controls/elevator", &tutorial_aircraft_interactive_plugin::elevator, { .minval = -1.f, .maxval = 1.f, .cenvel = 0.5f, .vel = 0.5f, .positions = 0 });
register_axis_handler("air/controls/brake", &tutorial_aircraft_interactive_plugin::brakes, {});
register_axis_handler("air/controls/aileron", &tutorial_aircraft_interactive_plugin::ailerons, { .minval = -1.f, .maxval = 1.f, .cenvel = 0.5f, .vel = 0.5f, .positions = 0 });
// Register handlers
// Actions
register_axis_handler("air/engines/throttle", &tutorial_aircraft_interactive_plugin::throttle, {});
register_axis_handler("air/controls/rudder", &tutorial_aircraft_interactive_plugin::rudder, {});
// Knobs
// Throttle lever interactive knob
actions::act_throttle_lever = register_axis_handler("air/engine/throttle", &tutorial_aircraft_interactive_plugin::throttle_knob, {});
// Rudder left pedal interactive knob
actions::act_rudder_pedal_L = register_axis_handler("knob_action_rudder_pedal_left", &tutorial_aircraft_interactive_plugin::rudder_knob_L, {});
// Rudder right pedal interactive knob
actions::act_rudder_pedal_R = register_axis_handler("knob_action_rudder_pedal_right", &tutorial_aircraft_interactive_plugin::rudder_knob_R, {});
// Brake left pedal interactive knob
actions::act_brake_pedal_L = register_axis_handler("knob_action_brake_pedal_left", &tutorial_aircraft_interactive_plugin::brake_knob_L, {});
// Brake right pedal interactive knob
actions::act_brake_pedal_R = register_axis_handler("knob_action_brake_pedal_right", &tutorial_aircraft_interactive_plugin::brake_knob_R, {});
return {
.mass = 1310,
.com_offset = {0.0f, 0.0f, 0.2f},
};
}
Note: in case of throttle lever, the knob has action name "air/engine/throttle", even though that action does not exist in air.cfg IOMap configuration (has "engine" instead of "engines" in name). Therefore handler for throttle action ("air/engines/throttle") has been previously added, so that it can react on changes in the throttle value. (for example when controlled through keyboard input).
Note: List of existing interactive knobs on the model can be found in Outerra -> Plugins -> Entity properties -> Bone attributes (model has to be selected).
Rest of the code remains almost unchanged, the only difference is, that the elevator, ailerons, rudder pedals, throttle handle and brake pedals animations were either removed (if there is no need to animate other parts, than the knobs, e.g. throttle handle and brake pedals), or moved into handlers, to avoid bugs (e.g. when the knob is grabbed in interactive mode, it is in conflict with the bone position setting/animating through script).
void tutorial_aircraft_interactive_plugin::initialize(bool reload)
{
this->jsbsim = jsb();
this->geom = get_geomob(0);
this->snd = sound();
set_fps_camera_pos({ 0.f, 1.f, 1.4f });
this->started = false;
this->braking = 0;
if (!this->jsbsim)
{
return;
}
this->jsbsim->operator()("propulsion/starter_cmd", 0);
this->jsbsim->operator()("fcs/right-brake-cmd-norm", 0);
this->jsbsim->operator()("fcs/left-brake-cmd-norm", 0);
this->jsbsim->operator()("fcs/throttle-cmd-norm[0]", 0);
this->jsbsim->operator()("fcs/mixture-cmd-norm[0]", 0);
this->snd->set_pitch(sources::rumble_exterior, 1);
this->snd->set_pitch(sources::rumble_interior, 1);
this->snd->set_pitch(sources::eng_exterior, 1);
this->snd->set_pitch(sources::eng_interior, 1);
this->snd->set_pitch(sources::prop_exterior, 1);
this->snd->set_pitch(sources::prop_interior, 1);
this->snd->set_pitch(sources::rumble_exterior, 1);
this->snd->set_pitch(sources::rumble_interior, 1);
this->snd->set_pitch(sources::eng_exterior, 1);
this->snd->set_pitch(sources::eng_interior, 1);
this->snd->set_pitch(sources::prop_exterior, 1);
this->snd->set_pitch(sources::prop_interior, 1);
}
void tutorial_aircraft_interactive_plugin::update_frame(float dt)
{
if (!this->jsbsim || !this->geom || !this->snd)
{
return;
}
float propeller_rpm = static_cast<float>(this->jsbsim->operator()("propulsion/engine[0]/propeller-rpm"));
float wheel_speed = static_cast<float>(this->jsbsim->operator()("gear/unit[0]/wheel-speed-fps"));
float elevator_pos_rad = static_cast<float>(this->jsbsim->operator()("fcs/elevator-pos-rad"));
this->geom->rotate_joint(bones::propeller, dt * (2 * static_cast<float>(PI)) * propeller_rpm, { 0.f, 1.f, 0.f });
this->geom->rotate_joint(bones::wheel_front, dt * static_cast<float>(PI) * (wheel_speed / 5), { -1.f, 0.f, 0.f });
this->geom->rotate_joint(bones::wheel_right, dt * static_cast<float>(PI) * (wheel_speed / 5), { -1.f, 0.f, 0.f });
this->geom->rotate_joint(bones::wheel_left, dt * static_cast<float>(PI) * (wheel_speed / 5), { -1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::flap_right, static_cast<float>(this->jsbsim->operator()("fcs/flap-pos-rad")), { 1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::flap_left, static_cast<float>(this->jsbsim->operator()("fcs/flap-pos-rad")), { 1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::rudder, static_cast<float>(this->jsbsim->operator()("fcs/rudder-pos-rad")), { 0.f, 0.f, -1.f });
this->geom->set_mesh_visible_id(meshes::prop_blur, propeller_rpm >= 200.f);
this->geom->set_mesh_visible_id(meshes::blade_one, propeller_rpm <= 300.f);
this->geom->set_mesh_visible_id(meshes::blade_two, propeller_rpm <= 300.f);
this->geom->set_mesh_visible_id(meshes::blade_three, propeller_rpm <= 300.f);
if (propeller_rpm > 0)
{
if (get_camera_mode() == 0)
{
this->snd->stop(sources::rumble_exterior);
this->snd->stop(sources::eng_exterior);
this->snd->stop(sources::prop_exterior);
this->snd->set_pitch(sources::eng_interior, clamp(1 + propeller_rpm / 4000.f, 1.f, 2.f));
this->snd->set_gain(sources::eng_interior, clamp(propeller_rpm / 5000.f, 0.f, 0.5f));
if (!this->snd->is_playing(sources::eng_interior))
{
this->snd->play_loop(sources::eng_interior, sounds::eng_interior);
}
this->snd->set_gain(sources::prop_interior, clamp(propeller_rpm / 7000.f, 0.f, 0.5f));
if (!this->snd->is_playing(sources::prop_interior))
{
this->snd->play_loop(sources::prop_interior, sounds::rumble);
}
this->snd->set_gain(sources::rumble_interior, clamp(propeller_rpm / 6000.f, 0.f, 1.5f));
if (!this->snd->is_playing(sources::rumble_interior))
{
this->snd->play_loop(sources::rumble_interior, sounds::rumble);
}
}
else
{
this->snd->stop(sources::eng_interior);
this->snd->stop(sources::prop_interior);
this->snd->stop(sources::rumble_interior);
this->snd->set_pitch(sources::eng_exterior, clamp(1 + propeller_rpm / 1000.f, 1.f, 3.f));
this->snd->set_gain(sources::eng_exterior, clamp(propeller_rpm / 450.f, 0.05f, 2.f));
if (!this->snd->is_playing(sources::eng_exterior))
{
this->snd->play_loop(sources::eng_exterior, sounds::eng_exterior);
}
this->snd->set_gain(sources::prop_exterior, clamp(propeller_rpm / 900.f, 0.f, 2.f));
if (!this->snd->is_playing(sources::prop_exterior))
{
this->snd->play_loop(sources::prop_exterior, sounds::eng_exterior);
}
this->snd->set_gain(sources::rumble_exterior, clamp(propeller_rpm / 1200.f, 0.f, 2.f));
if (!this->snd->is_playing(sources::rumble_exterior))
{
this->snd->play_loop(sources::rumble_exterior, sounds::rumble);
}
}
}
else
{
this->snd->stop(sources::eng_exterior);
this->snd->stop(sources::eng_interior);
this->snd->stop(sources::prop_exterior);
this->snd->stop(sources::prop_interior);
this->snd->stop(sources::rumble_exterior);
this->snd->stop(sources::rumble_interior);
}
if (!this->started && propeller_rpm < 5)
{
this->jsbsim->operator()("fcs/center-brake-cmd-norm", 1);
this->jsbsim->operator()("fcs/left-brake-cmd-norm", 1);
this->jsbsim->operator()("fcs/right-brake-cmd-norm", 1);
}
else if (this->braking < 0.1)
{
this->jsbsim->operator()("fcs/center-brake-cmd-norm", 0);
this->jsbsim->operator()("fcs/left-brake-cmd-norm", 0);
this->jsbsim->operator()("fcs/right-brake-cmd-norm", 0);
}
}
Test