Outerra forum

Anteworld - Outerra Game => Modding: Importer, Tools & Utilities => JavaScript development => Topic started by: Uriah on September 21, 2015, 12:29:38 pm

Title: Extended Controls
Post by: Uriah on September 21, 2015, 12:29:38 pm
I thought it might be helpful to do an overview of the new extended control system.

There is a readme document located in the build directory: Anteworld\defaults\iomap\readme.txt 

The config files located in your data directory iomap folder are the ones which are modified when you configure your controls using the UI, typically located in C:\Users\YourName\Outerra\iomap\

When you delete a config file from this folder, air.cfg for example, and run OT again it is replaced with the default config from Anteworld\defaults\iomap\readme.txt

When you open one of the config files, you'll see the following structure:

Code: [Select]
"name": "controls",
            "actions": [
                    "name": "elevator",
                    "comment": "Elevator up/center/down",
                    "bindings": [
                            "event_name": "W",
                            "mode": "-"
                            "event_name": "S",
                            "mode": "+"
                            "event_name": "Axis0",
                            "mode": "+"

In this case "controls" is the action group, under which a number of controls are located, including "elevator", etc, which may have a number of different key bindings. Many of these controls use default handlers for aircraft, which means you do not need to implement them in JavaScript to set a JSBSim property. You'll notice the mode for each binding is either "-" or "+", there are a number of other modes you can learn more about in the readme, for increments and toggles.

Now let's say you want to implement a control to turn a light, or group of lights, on and off. In this case you'll use the method register_event to set the variable landing_light_switch to either 0 or 1. The register methods are always implemented in the initialize(reload) function of the script, and can be used to directly set a global variable or jsb property. You'll notice I set the variable to 0 above the register_event method, which in initialize(reload) sets the light switch to the off position when the aircraft script is initialized. Alternatively you could always set it to on, but I chose the initial state to be off for performance reasons, allowing users to turn on lights as desired. Remember to always declare your global variables at the top of the script.

Code: [Select]
landing_light_switch = 0;
this.register_event("air/lights/landing_lights", function(v) { landing_light_switch ^= 1; });

The first parameter is a string "air/lights/landing_lights", which specifies the config file, in this case air.cfg, the action group "lights", and the control "landing_lights". This method is setup to toggle the parameter v off/on (0/1), setting the variable landing_light_switch, which in this particular case is switched using the 'L' key.

A more complicated control is one I developed for dynamic in-cockpit zoom in/out function. It is already implemented on both the mig29 and c172.

First we must call the OT World Interface in initialize(reload) function, and set it to a global variable, in this case ot_world is declared at the top of the script.

Code: [Select]
ot_world = this.$query_interface("ot::js::world.get");
This interface will give the script access to a method to set the horizontal field of view (HFOV) later in update_frame(dt). Here is the extended control function.

Code: [Select]
fov_cockpit_zoom = 81.8;
this.register_axis("air/sensor/fov_zoom", {minval:0, maxval:1, vel:1, center:0}, function(v){
  var dec2int = v*48;
  var integer = Math.floor(dec2int);
  var string = 81.8 - integer.toString();
  fov_cockpit_zoom = string;

You'll notice I used the register_axis method instead of the register_event method, which increments the output between 0 and 1. I have included parameters to define; minval, maxval, vel, and center... read more about these in the readme doc. I have declared the global variable fov_cockpit_zoom at the top of the script, and here I am setting it to an initial value of 81.8, which will set the initial camera HFOV. In the function, I have a number of local variables. dec2int multiples the parameter v by a fixed value, transforming a decimal between 0 to 1 into an integer between 0 to 48. The next variable integer simply uses the math function Math.floor to force the number to be an integer, this is optional. The next variable string subtracts the integer from out initial value of 81.8, so as the parameter v increases our output decreases, therefore zooming in. Finally, our variable fov_cockpit_zoom is set to the output. The final step is to call a method in update_frame(dt) which sets the HFOV for the camera, using the method set_camera_fov. I use an if statement to only set the FOV is camera mode is 0 or FPS mode.

Code: [Select]
//Set FPS camera FOV OT World Interface
if (this.get_camera_mode() == 0){

This should give you a small taste of what is possible with the extend controls.

Best regards,

The default handlers for aircraft are as follow:

Key Bind:             Function:

W / S                    Elevator (longitudinal cyclic for heli)
A / D                     Aileron (lateral cyclic for heli)
Z / X                     Rudder (anti torque for heli)
Ctrl + W / S          Elevator Trim
Ctrl + A / D           Aileron Trim
Ctrl + Z / X           Rudder Trim
Insert                    Flaps Down
Delete                   Flaps Up
E                          Engine on/off
PgUp                    Throttle Up
PgDn                    Throttle Down
Period ( . )            Landing Gear
B                          Brakes         
Home                    Mixture Up
End                       Mixture Down
R / F                      Collective (heli)
Shift + A               Altitude Hold (heli)
Shift + S               Stability Augmentation System (heli)

I have added some basic extended controls to the mig29 and c172 scripts which are as follow.

Key Bind:             Function:

C                         Canopy open/close
L                         Landing Lights on/off
Shift + L              Cockpit Lights on/off
Shift + N             Nav Lights on/off
Ctrl + L               Strobe Lights on/off
Shift + W            Wing Level Autopilot on/off
NumAdd ( + )      Zoom In (Number Pad)
NumSub ( - )        Zoom Out (Number Pad)
P                         Deploy/Cut Braking Parachute (mig29)
Title: Re: Extended Controls
Post by: andfly on September 23, 2015, 02:22:55 pm
Thanks!....Thanks!......Thanks!...................   T H A N K S !!!!!   :)
Title: Re: Extended Controls
Post by: SteelRat on September 26, 2015, 12:25:00 pm
Hi Uriah!

Code: [Select]
    name : "transmission",
    actions : [
{ name : "up_gear", comment : "Up gear", bindings : [{event_name:"PageUp"}], important:true },
{ name : "down_gear", comment : "Down gear", bindings : [{event_name:"PageDown"}], important:true }

What sets this property?
Code: [Select]
Title: Re: Extended Controls
Post by: Uriah on September 27, 2015, 05:45:04 pm
I believe that is for the "Show important actions only" check box in the Controls UI, which currently seems to not be functional. It is meant so you can filter the long list of controls to only the important ones, but currently you'll notice the check box says "Show advanced controls", and when that is not checked the list is blank. Cameni said it isn't finished yet.

Title: Re: Extended Controls
Post by: SteelRat on September 27, 2015, 07:25:57 pm
Title: Re: Extended Controls
Post by: zzz on April 23, 2016, 01:39:57 pm
I'm having trouble with the air/controls section. None of them seem to register.

this.register_event("air/controls/elevator", {minval:-1, center:1000, vel:1000, acc:1000}, function(v) {

With car controls and airplane weapons it shows me the value of V but not with air controls.
Title: Re: Extended Controls
Post by: Uriah on April 23, 2016, 01:43:49 pm

1. Declare a global variable named test_var
2. Assign v to test_var in the register_event function
3. log_inf for test_var is update_frame
Title: Re: Extended Controls
Post by: Uriah on April 23, 2016, 02:09:23 pm
Also, there is a problem, syntax error unexpected identifier, because you didn't close the method.

Code: [Select]
  this.register_axis("air/controls/elevator", {minval:-1, center:1000, vel:1000, acc:1000}, function(v) {
    test_var = v;


I am able to log_inf from inside other register_axis or register_event methods functions perfectly fine. Also, for something like elevator controls, you would want to use register_axis method. Register event would be used for switches, like opening and closing a door, lights, etc... register_axis is used for controls which have a range of input stages, such as a joystick axis.

Title: Re: Extended Controls
Post by: Uriah on April 23, 2016, 02:25:10 pm
You can also assign jsbsim properties directly inside the register_axis method:

Code: [Select]
  this.register_axis("air/controls/elevator", {minval:-1, maxval:1, vel:0.1}, function(v){
Title: Re: Extended Controls
Post by: zzz on April 23, 2016, 05:51:28 pm
no, none of those are working. I can get Canopy to work, but any buttons with + and - next to them in the controls don't register at all.

My full JS file:

//aircraft script file
//see http://xtrac.outerraworld.com/trac.fcgi/wiki/aircraft for example and documentation

var fc = 0;
var ammo = 125;

const MaxTurretProjSpeed01 = 1025; // m/s bullet speed
const MaxTurretProjSize01 = 1;
const MaxTurretDev01 = 0.5;
const flapSpeed = 10;
const flapAccel = 100;
const flapMinRange = -35;
const flapMaxRange = 35;

function radians(v){
  return v*Math.PI/180.0;

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;

function MoA(deviance) {
   r1 = getRandomInt(1, 10001)/10000;
   r2 = getRandomInt(1, 10001)/10000;
   comp1 = (r1/(r1+r2)) * Math.pow(deviance, 2);
   comp2 = (r2/(r1+r2)) * Math.pow(deviance, 2);
   r3 = getRandomInt(1, 5);
   r4 = getRandomInt(1, Math.sqrt(comp1)*10000)/10000;
   r5 = getRandomInt(1, Math.sqrt(comp2)*10000)/10000;
   if (r3 == 1) {
      return [r4, r5];
   } else if (r3 == 2) {
      return [-r4, r5];
   } else if (r3 == 3) {
      return [r4, -r5];
   } else {
      return [-r4, -r5];

function axis_integrator(maxspeed,maxaccel,clamp_min,clamp_max){
  this.mac = maxaccel ? maxaccel : 1.0;
  this.msp = maxspeed;
  this.speed = 0;
  this.value = 0;
  this.targ = 0;
  this.cmin = clamp_min!=undefined ? clamp_min : Number.NEGATIVE_INFINITY;
  this.cmax = clamp_max!=undefined ? clamp_max : Number.POSITIVE_INFINITY;

axis_integrator.prototype.set = function(v){this.targ = v}
axis_integrator.prototype.set_max_speed = function(v){this.msp = v}
axis_integrator.prototype.changed = function(dt){
  var ob = this.speed;
  var dm = this.mac*dt;
  if(this.targ > ob+dm)
    this.speed += dm;
  else if(this.targ < ob-dm)
    this.speed -= dm;
    this.speed = this.targ;
  var ov = this.value;
  this.value += this.speed*this.msp*dt;
  if(this.value < this.cmin) this.value = this.cmin;
  if(this.value > this.cmax) this.value = this.cmax;
  return ov != this.value;

//aircraft instance initialization
//params - parameter object from objdef
function initialize(params){
  this.geom = this.get_geomob(0);
this.canL = this.geom.get_joint("LCanardP");
this.canR = this.geom.get_joint("RCanardP");
this.rud = this.geom.get_joint("RudderP");
this.flapL = this.geom.get_joint("LFlapP");
this.flapLL = this.geom.get_joint("LLFlapP");
this.flapR = this.geom.get_joint("RFlapP");
this.flapRR = this.geom.get_joint("RRFlapP");

  this.fL = new axis_integrator(radians(flapSpeed), radians(flapAccel), radians(flapMinRange), radians(flapMaxRange));

  this.register_axis("air/weapons/fire_any_armed", {minval:0, center:1000, vel:1000, acc:1000}, function(v) {
   if (v==1) {
      fireLaser = 1;
   soundNo = getRandomInt(0,5);
   //this.snd.play(0, soundNo, 0, 0, 0);

this.register_event("air/controls/elevator", {minval:0, center:1000, vel:1000, acc:1000}, function(v) {


//invoked each frame to handle the inputs and animate the model
function update_frame(dt){
 if (fc > 1 && fireLaser == 1 && ammo > 0) {
   dev01 = MoA(radians(MaxTurretDev01));
this.fire({x:1.2385,y:2.165,z:-0.302}, {x:Math.sin(dev01[0])*Math.cos(dev01[1]),y:Math.cos(dev01[0])*Math.cos(dev01[1]),z:-Math.sin(dev01[1])}, MaxTurretProjSpeed01, MaxTurretProjSize01, {x:255/255,y:111/255,z:15/255});
 fc = 0;
 ammo = ammo - 1;
 } else {
   fc = fc +1;

if (ammo < 1 && fireLaser == 1) {
   this.log_inf('OUT OF AMMO')

    this.geom.rotate_joint_orig(this.canL, radians(-flapMaxRange)+this.fL.value, {x:1});

Title: Re: Extended Controls
Post by: Uriah on April 23, 2016, 08:21:56 pm
Let me take a closer look. Everything was working before the public release of version 6601, however now I am noticing a number of issues.
Title: Re: Extended Controls
Post by: zzz on May 15, 2016, 01:13:05 pm
I'm using JSB commands to move the geometry, but is there a way to get a geom object to respond to two different JSB commands?

If I have this:

Code: [Select]
this.geom.rotate_joint_orig(this.canL, this.jsbsim['fcs/left-aileron-pos-rad'], {x:1,y:0,z:0});
this.geom.rotate_joint_orig(this.canL, this.jsbsim['fcs/elevator-pos-rad'], {x:-1,y:0,z:0});

it only responds to elevator activity, and I have surfaces that act as both ailerons and elevators.
Title: Re: Extended Controls
Post by: Uriah on May 15, 2016, 01:46:42 pm
Just use a mixing function for flaperons/tailerons/elevons, etc. Representing that control surface in JSBSim FCS and declaring a new property for it to output is also possible.

Title: Re: Extended Controls
Post by: cameni on May 15, 2016, 02:45:06 pm
rotate_joint_orig always applies rotation to the base orientation. The second one should be just rotate_joint, accumulative one.