a4f289ecee
This also makes sure the /canvas/by-index/canvas[3/4]/ nodes are removed and then recrated, as well as making sure the MapStructure del() path is followed and working. Unfortunately the NDs are still blank after reinit.
1044 lines
43 KiB
Text
1044 lines
43 KiB
Text
# ==============================================================================
|
|
# Boeing Navigation Display by Gijs de Rooy
|
|
# ==============================================================================
|
|
|
|
##
|
|
# do we really need to keep track of each drawable here ??
|
|
var i = 0;
|
|
|
|
##
|
|
# pseudo DSL-ish: use these as placeholders in the config hash below
|
|
var ALWAYS = func 1;
|
|
var NOTHING = func nil;
|
|
|
|
##
|
|
# so that we only need to update a single line ...
|
|
#
|
|
var trigger_update = func(layer) layer._model.init();
|
|
|
|
##
|
|
# TODO: move ND-specific implementation details into this lookup hash
|
|
# so that other aircraft and ND types can be more easily supported
|
|
#
|
|
# any aircraft-specific ND behavior should be wrapped here,
|
|
# to isolate/decouple things in the generic NavDisplay class
|
|
#
|
|
# TODO: move this to an XML config file
|
|
#
|
|
var NDStyles = {
|
|
##
|
|
# this configures the 744 ND to help generalize the NavDisplay class itself
|
|
'Boeing': {
|
|
font_mapper: func(family, weight) {
|
|
if( family == "Liberation Sans" and weight == "normal" )
|
|
return "LiberationFonts/LiberationSans-Regular.ttf";
|
|
},
|
|
|
|
# where all the symbols are stored
|
|
# TODO: SVG elements should be renamed to use boeing/airbus prefix
|
|
# aircraft developers should all be editing the same ND.svg image
|
|
# the code can deal with the differences now
|
|
svg_filename: "Nasal/canvas/map/boeingND.svg",
|
|
##
|
|
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
|
|
##
|
|
|
|
layers: [
|
|
{ name:'fixes', disabled:1, update_on:['toggle_range','toggle_waypoints'],
|
|
predicate: func(nd, layer) {
|
|
# print("Running fixes predicate");
|
|
var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
|
if (visible) {
|
|
# print("fixes update requested!");
|
|
trigger_update( layer );
|
|
}
|
|
layer._view.setVisible(visible);
|
|
}, # end of layer update predicate
|
|
}, # end of fixes layer
|
|
{ name:'FIX', isMapStructure:1, update_on:['toggle_range','toggle_waypoints'],
|
|
# FIXME: this is a really ugly place for controller code
|
|
predicate: func(nd, layer) {
|
|
# print("Running vor layer predicate");
|
|
# toggle visibility here
|
|
var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
|
layer.group.setVisible( nd.get_switch('toggle_waypoints') );
|
|
if (visible) {
|
|
#print("Updating MapStructure ND layer: FIX");
|
|
# (Hopefully) smart update
|
|
layer.update();
|
|
}
|
|
}, # end of layer update predicate
|
|
}, # end of FIX layer
|
|
# Should redraw every 10 seconds
|
|
{ name:'storms', update_on:['toggle_range','toggle_weather','toggle_display_mode'],
|
|
predicate: func(nd, layer) {
|
|
# print("Running fixes predicate");
|
|
var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
|
|
if (visible) {
|
|
#print("storms update requested!");
|
|
trigger_update( layer );
|
|
}
|
|
layer._view.setVisible(visible);
|
|
}, # end of layer update predicate
|
|
}, # end of storms layer
|
|
|
|
{ name:'airports-nd', update_on:['toggle_range','toggle_airports','toggle_display_mode'],
|
|
predicate: func(nd, layer) {
|
|
# print("Running airports-nd predicate");
|
|
var visible = nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
|
|
if (visible) {
|
|
trigger_update( layer ); # clear & redraw
|
|
}
|
|
layer._view.setVisible( visible );
|
|
}, # end of layer update predicate
|
|
}, # end of airports layer
|
|
|
|
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
|
|
{ name:'vor', disabled:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
|
|
predicate: func(nd, layer) {
|
|
# print("Running vor layer predicate");
|
|
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
|
if(visible) {
|
|
trigger_update( layer ); # clear & redraw
|
|
}
|
|
layer._view.setVisible( nd.get_switch('toggle_stations') );
|
|
}, # end of layer update predicate
|
|
}, # end of VOR layer
|
|
{ name:'VOR', isMapStructure:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
|
|
# FIXME: this is a really ugly place for controller code
|
|
predicate: func(nd, layer) {
|
|
# print("Running vor layer predicate");
|
|
# toggle visibility here
|
|
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
|
layer.group.setVisible( visible );
|
|
if (visible) {
|
|
#print("Updating MapStructure ND layer: VOR");
|
|
# (Hopefully) smart update
|
|
layer.update();
|
|
}
|
|
}, # end of layer update predicate
|
|
}, # end of VOR layer
|
|
|
|
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
|
|
{ name:'dme', disabled:1, update_on:['toggle_range','toggle_stations'],
|
|
predicate: func(nd, layer) {
|
|
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
|
|
if(visible) {
|
|
trigger_update( layer ); # clear & redraw
|
|
}
|
|
layer._view.setVisible( nd.get_switch('toggle_stations') );
|
|
}, # end of layer update predicate
|
|
}, # end of DME layers
|
|
{ name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
|
|
# FIXME: this is a really ugly place for controller code
|
|
predicate: func(nd, layer) {
|
|
# print("Running vor layer predicate");
|
|
# toggle visibility here
|
|
layer.group.setVisible( nd.get_switch('toggle_stations') );
|
|
if (nd.rangeNm() <= 40 and
|
|
nd.get_switch('toggle_stations') and
|
|
nd.get_switch('toggle_display_mode') == "MAP") {
|
|
#print("Updating MapStructure ND layer: DME");
|
|
# (Hopefully) smart update
|
|
layer.update();
|
|
}
|
|
}, # end of layer update predicate
|
|
}, # end of DME layer
|
|
|
|
{ name:'mp-traffic', update_on:['toggle_range','toggle_traffic'],
|
|
predicate: func(nd, layer) {
|
|
trigger_update( layer ); # clear & redraw
|
|
layer._view.setVisible( 1 ); #nd.get_switch('toggle_traffic')
|
|
}, # end of layer update predicate
|
|
}, # end of traffic layer
|
|
|
|
{ name:'runway-nd', update_on:['toggle_range','toggle_display_mode'],
|
|
predicate: func(nd, layer) {
|
|
var visible = (nd.rangeNm() <= 40) and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP','PLAN']) ;
|
|
if (visible)
|
|
trigger_update( layer ); # clear & redraw
|
|
layer._view.setVisible( visible );
|
|
}, # end of layer update predicate
|
|
}, # end of airports-nd layer
|
|
|
|
{ name:'route', update_on:['toggle_range','toggle_display_mode'],
|
|
predicate: func(nd, layer) {
|
|
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
|
|
if (visible)
|
|
trigger_update( layer ); # clear & redraw
|
|
layer._view.setVisible( visible );
|
|
}, # end of layer update predicate
|
|
}, # end of route layer
|
|
|
|
## add other layers here, layer names must match the registered names as used in *.layer files for now
|
|
## this will all change once we're using Philosopher's MapStructure framework
|
|
|
|
], # end of vector with configured layers
|
|
|
|
# This is where SVG elements are configured by providing "behavior" hashes, i.e. for animations
|
|
|
|
# to animate each SVG symbol, specify behavior via callbacks (predicate, and true/false implementation)
|
|
# SVG identifier, callback etc
|
|
# TODO: update_on([]), update_mode (update() vs. timers/listeners)
|
|
# TODO: support putting symbols on specific layers
|
|
features: [
|
|
{
|
|
# TODO: taOnly doesn't need to use getprop polling in update(), use a listener instead!
|
|
id: 'taOnly', # the SVG ID
|
|
impl: { # implementation hash
|
|
init: func(nd, symbol), # for updateCenter stuff, called during initialization in the ctor
|
|
predicate: func(nd) getprop("instrumentation/tcas/inputs/mode") == 2, # the condition
|
|
is_true: func(nd) nd.symbols.taOnly.show(), # if true, run this
|
|
is_false: func(nd) nd.symbols.taOnly.hide(), # if false, run this
|
|
}, # end of taOnly behavior/callbacks
|
|
}, # end of taOnly
|
|
{
|
|
id: 'tas',
|
|
impl: {
|
|
init: func(nd,symbol),
|
|
predicate: func(nd) getprop("/velocities/airspeed-kt") > 100,
|
|
is_true: func(nd) {
|
|
nd.symbols.tas.setText(sprintf("%3.0f",getprop("/velocities/airspeed-kt") ));
|
|
nd.symbols.tas.show();
|
|
},
|
|
is_false: func(nd) nd.symbols.tas.hide(),
|
|
}, # end of tas behavior callbacks
|
|
}, # end of tas hash
|
|
{
|
|
id: 'wpActiveId',
|
|
impl: {
|
|
init: func(nd,symbol),
|
|
predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active"),
|
|
is_true: func(nd) {
|
|
nd.symbols.wpActiveId.setText(getprop("/autopilot/route-manager/wp/id"));
|
|
nd.symbols.wpActiveId.show();
|
|
},
|
|
is_false: func(nd) nd.symbols.wpActiveId.hide(),
|
|
}, # of wpActiveId.impl
|
|
}, # of wpActiveId
|
|
{
|
|
id: 'wpActiveDist',
|
|
impl: {
|
|
init: func(nd,symbol),
|
|
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active"),
|
|
is_true: func(nd) {
|
|
nd.symbols.wpActiveDist.setText(sprintf("%3.01fNM",getprop("/autopilot/route-manager/wp/dist")));
|
|
nd.symbols.wpActiveDist.show();
|
|
},
|
|
is_false: func(nd) nd.symbols.wpActiveDist.hide(),
|
|
}, # of wpActiveDist.impl
|
|
}, # of wpActiveDist
|
|
{
|
|
id: 'eta',
|
|
impl: {
|
|
init: func(nd,symbol),
|
|
predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active"),
|
|
is_true: func(nd) {
|
|
var etaSec = getprop("/sim/time/utc/day-seconds")+getprop("autopilot/route-manager/wp/eta-seconds");
|
|
var h = math.floor(etaSec/3600);
|
|
if (h>24) h=h-24;
|
|
etaSec=etaSec-3600*h;
|
|
var m = math.floor(etaSec/60);
|
|
etaSec=etaSec-60*m;
|
|
var s = etaSec;
|
|
nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%02.0fz",h,m,s));
|
|
nd.symbols.eta.show();
|
|
},
|
|
is_false: func(nd) nd.symbols.eta.hide(),
|
|
}, # of eta.impl
|
|
}, # of eta
|
|
{
|
|
id:'hdg',
|
|
impl: {
|
|
init: func(nd,symbol),
|
|
predicate: ALWAYS, # always true
|
|
is_true: func(nd) nd.symbols.hdg.setText(sprintf("%03.0f", nd.aircraft_source.get_hdg_mag() )),
|
|
is_false: NOTHING,
|
|
}, # of hdg.impl
|
|
}, # of hdg
|
|
{
|
|
id:'gs',
|
|
impl: {
|
|
init: func(nd,symbol),
|
|
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_spd() )),
|
|
predicate: func(nd) nd.aircraft_source.get_spd() >= 30,
|
|
is_true: func(nd) {
|
|
nd.symbols.gs.setFontSize(36);
|
|
},
|
|
is_false: func(nd) nd.symbols.gs.setFontSize(52),
|
|
}, # of gs.impl
|
|
}, # of gs
|
|
{
|
|
id:'rangeArcs',
|
|
impl: {
|
|
init: func(nd,symbol),
|
|
predicate: func(nd) ((((nd.get_switch('toggle_display_mode') == "APP" or nd.get_switch('toggle_display_mode') == "VOR") and nd.get_switch('toggle_weather')) or nd.get_switch('toggle_display_mode') == "MAP") and (!nd.get_switch('toggle_centered'))),
|
|
is_true: func(nd) nd.symbols.rangeArcs.show(),
|
|
is_false: func(nd) nd.symbols.rangeArcs.hide(),
|
|
}, # of rangeArcs.impl
|
|
}, # of rangeArcs
|
|
|
|
|
|
], # end of vector with features
|
|
|
|
|
|
}, # end of Boeing style
|
|
#####
|
|
##
|
|
## add support for other aircraft/ND types and styles here (Airbus etc)
|
|
##
|
|
##
|
|
|
|
}; # end of NDStyles
|
|
|
|
##
|
|
# encapsulate hdg/lat/lon source, so that the ND may also display AI/MP aircraft in a pilot-view at some point (aka stress-testing)
|
|
#
|
|
|
|
var NDSourceDriver = {};
|
|
NDSourceDriver.new = func {
|
|
var m = {parents:[NDSourceDriver]};
|
|
m.get_hdg_mag= func getprop("/orientation/heading-magnetic-deg");
|
|
m.get_hdg_tru= func getprop("/orientation/heading-deg");
|
|
m.get_hgg = func getprop("instrumentation/afds/settings/heading");
|
|
m.get_trk_mag= func
|
|
{
|
|
if(getprop("/velocities/groundspeed-kt") > 80)
|
|
{
|
|
getprop("/orientation/track-magnetic-deg");
|
|
}
|
|
else
|
|
{
|
|
getprop("/orientation/heading-magnetic-deg");
|
|
}
|
|
};
|
|
m.get_trk_tru = func
|
|
{
|
|
if(getprop("/velocities/groundspeed-kt") > 80)
|
|
{
|
|
getprop("/orientation/track-deg");
|
|
}
|
|
else
|
|
{
|
|
getprop("/orientation/heading-deg");
|
|
}
|
|
};
|
|
m.get_lat= func getprop("/position/latitude-deg");
|
|
m.get_lon= func getprop("/position/longitude-deg");
|
|
m.get_spd= func getprop("/velocities/groundspeed-kt");
|
|
m.get_vspd= func getprop("/velocities/vertical-speed-fps");
|
|
return m;
|
|
}
|
|
|
|
##
|
|
# configure aircraft specific cockpit switches here
|
|
# these are some defaults, can be overridden when calling NavDisplay.new() -
|
|
# see the 744 ND.nas file the backend code should never deal directly with
|
|
# aircraft specific properties using getprop.
|
|
# To get started implementing your own ND, just copy the switches hash to your
|
|
# ND.nas file and map the keys to your cockpit properties - and things will just work.
|
|
|
|
# TODO: switches are ND specific, so move to the NDStyle hash!
|
|
|
|
var default_switches = {
|
|
'toggle_range': {path: '/inputs/range-nm', value:40, type:'INT'},
|
|
'toggle_weather': {path: '/inputs/wxr', value:0, type:'BOOL'},
|
|
'toggle_airports': {path: '/inputs/arpt', value:0, type:'BOOL'},
|
|
'toggle_stations': {path: '/inputs/sta', value:0, type:'BOOL'},
|
|
'toggle_waypoints': {path: '/inputs/wpt', value:0, type:'BOOL'},
|
|
'toggle_position': {path: '/inputs/pos', value:0, type:'BOOL'},
|
|
'toggle_data': {path: '/inputs/data',value:0, type:'BOOL'},
|
|
'toggle_terrain': {path: '/inputs/terr',value:0, type:'BOOL'},
|
|
'toggle_traffic': {path: '/inputs/tcas',value:0, type:'BOOL'},
|
|
'toggle_centered': {path: '/inputs/nd-centered',value:0, type:'BOOL'},
|
|
'toggle_lh_vor_adf': {path: '/inputs/lh-vor-adf',value:0, type:'INT'},
|
|
'toggle_rh_vor_adf': {path: '/inputs/rh-vor-adf',value:0, type:'INT'},
|
|
'toggle_display_mode': {path: '/mfd/display-mode', value:'MAP', type:'STRING'}, # valid values are: APP, MAP, PLAN or VOR
|
|
'toggle_display_type': {path: '/mfd/display-type', value:'CRT', type:'STRING'}, # valid values are: CRT or LCD
|
|
'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'},
|
|
};
|
|
|
|
# Hack to update weather radar once every 10 seconds
|
|
var update_weather = func {
|
|
if (getprop("/instrumentation/efis/inputs/wxr") != nil)
|
|
setprop("/instrumentation/efis/inputs/wxr",getprop("/instrumentation/efis/inputs/wxr"));
|
|
settimer(update_weather, 10);
|
|
}
|
|
update_weather();
|
|
|
|
# Hack to update airplane symbol location on PLAN mode every 5 seconds
|
|
var update_apl_sym = func {
|
|
if (getprop("/instrumentation/efis/mfd/display-mode") == "PLAN")
|
|
setprop("/instrumentation/efis/mfd/display-mode","PLAN");
|
|
settimer(update_apl_sym, 5);
|
|
}
|
|
update_apl_sym();
|
|
|
|
##
|
|
# TODO:
|
|
# - introduce a MFD class (use it also for PFD/EICAS)
|
|
# - introduce a SGSubsystem class and use it here
|
|
# - introduce a Boeing NavDisplay class
|
|
var NavDisplay = {
|
|
|
|
# reset handler
|
|
handle_reinit: func {
|
|
print("Cleaning up NavDisplay");
|
|
# shut down all timers and other loops here
|
|
me.update_timer.stop();
|
|
foreach(var l; me.listeners)
|
|
removelistener(l);
|
|
# clean up MapStructure
|
|
me.map.del();
|
|
# destroy the canvas
|
|
if (me.canvas_handle != nil)
|
|
me.canvas_handle.del();
|
|
me.inited = 0;
|
|
},
|
|
|
|
listen: func(p,c) {
|
|
append(me.listeners, setlistener(p,c));
|
|
},
|
|
|
|
# listeners for cockpit switches
|
|
listen_switch: func(s,c) {
|
|
# print("event setup for: ", id(c));
|
|
me.listen( me.get_full_switch_path(s), func {
|
|
# print("listen_switch triggered:", s, " callback id:", id(c) );
|
|
c();
|
|
});
|
|
},
|
|
|
|
# get the full property path for a given switch
|
|
get_full_switch_path: func (s) {
|
|
# debug.dump( me.efis_switches[s] );
|
|
return me.efis_path ~ me.efis_switches[s].path; # FIXME: should be using props.nas instead of ~
|
|
},
|
|
|
|
# helper method for getting configurable cockpit switches (which are usually different in each aircraft)
|
|
get_switch: func(s) {
|
|
var switch = me.efis_switches[s];
|
|
var path = me.efis_path ~ switch.path ;
|
|
#print(s,":Getting switch prop:", path);
|
|
|
|
return getprop( path );
|
|
},
|
|
|
|
# for creating NDs that are driven by AI traffic instead of the main aircraft (generalization rocks!)
|
|
connectAI: func(source=nil) {
|
|
me.aircraft_source = {
|
|
get_hdg_mag: func source.getNode('orientation/heading-magnetic-deg').getValue(),
|
|
get_trk_mag: func source.getNode('orientation/track-magnetic-deg').getValue(),
|
|
get_lat: func source.getNode('position/latitude-deg').getValue(),
|
|
get_lon: func source.getNode('position/longitude-deg').getValue(),
|
|
get_spd: func source.getNode('velocities/true-airspeed-kt').getValue(),
|
|
};
|
|
}, # of connectAI
|
|
|
|
# TODO: the ctor should allow customization, for different aircraft
|
|
# especially properties and SVG files/handles (747, 757, 777 etc)
|
|
new : func(prop1, switches=default_switches, style='Boeing') {
|
|
var m = { parents : [NavDisplay]};
|
|
|
|
m.inited = 0;
|
|
|
|
m.listeners=[]; # for cleanup handling
|
|
m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading)
|
|
|
|
m.nd_style = NDStyles[style]; # look up ND specific stuff (file names etc)
|
|
|
|
m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies",
|
|
"instrumentation/nav/frequencies", "instrumentation/nav[1]/frequencies"];
|
|
m.mfd_mode_list=["APP","VOR","MAP","PLAN"];
|
|
|
|
m.efis_path = prop1;
|
|
m.efis_switches = switches;
|
|
|
|
# just an alias, to avoid having to rewrite the old code for now
|
|
m.rangeNm = func m.get_switch('toggle_range');
|
|
|
|
m.efis = props.globals.initNode(prop1);
|
|
m.mfd = m.efis.initNode("mfd");
|
|
|
|
# TODO: unify this with switch handling
|
|
m.mfd_mode_num = m.mfd .initNode("mode-num",2,"INT");
|
|
m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL");
|
|
m.previous_set = m.efis.initNode("inhg-previous",29.92);
|
|
m.kpa_mode = m.efis.initNode("inputs/kpa-mode",0,"BOOL");
|
|
m.kpa_output = m.efis.initNode("inhg-kpa",29.92);
|
|
m.kpa_prevoutput = m.efis.initNode("inhg-kpa-previous",29.92);
|
|
m.temp = m.efis.initNode("fixed-temp",0);
|
|
m.alt_meters = m.efis.initNode("inputs/alt-meters",0,"BOOL");
|
|
m.fpv = m.efis.initNode("inputs/fpv",0,"BOOL");
|
|
|
|
m.mins_mode = m.efis.initNode("inputs/minimums-mode",0,"BOOL");
|
|
m.mins_mode_txt = m.efis.initNode("minimums-mode-text","RADIO","STRING");
|
|
m.minimums = m.efis.initNode("minimums",250,"INT");
|
|
m.mk_minimums = props.globals.getNode("instrumentation/mk-viii/inputs/arinc429/decision-height");
|
|
|
|
# TODO: these are switches, can be unified with switch handling hash above (eventually):
|
|
m.nd_plan_wpt = m.efis.initNode("inputs/plan-wpt-index", 0, "INT"); # not yet in switches hash
|
|
|
|
###
|
|
# initialize all switches based on the defaults specified in the switch hash
|
|
#
|
|
foreach(var switch; keys( m.efis_switches ) )
|
|
props.globals.initNode
|
|
( m.get_full_switch_path (switch),
|
|
m.efis_switches[switch].value,
|
|
m.efis_switches[switch].type
|
|
);
|
|
|
|
|
|
return m;
|
|
},
|
|
newMFD: func(canvas_group, parent=nil)
|
|
{
|
|
if (me.inited) die("MFD already was added to scene");
|
|
me.inited = 1;
|
|
me.listen("/sim/signals/reinit", func(n) me.handle_reinit() );
|
|
me.update_timer = maketimer(0.05, func me.update() ); # TODO: make interval configurable via ctor
|
|
me.nd = canvas_group;
|
|
me.canvas_handle = parent;
|
|
|
|
# load the specified SVG file into the me.nd group and populate all sub groups
|
|
|
|
canvas.parsesvg(me.nd, me.nd_style.svg_filename, {'font-mapper': me.nd_style.font_mapper});
|
|
me.symbols = {}; # storage for SVG elements, to avoid namespace pollution (all SVG elements end up here)
|
|
|
|
foreach(var feature; me.nd_style.features ) {
|
|
# print("Setting up SVG feature:", feature.id);
|
|
me.symbols[feature.id] = me.nd.getElementById(feature.id);
|
|
if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter)
|
|
}
|
|
|
|
### this is the "old" method that's less flexible, we want to use the style hash instead (see above)
|
|
# because things are much better configurable that way
|
|
# now look up all required SVG elements and initialize member fields using the same name to have a convenient handle
|
|
foreach(var element; ["wind","dmeLDist","dmeRDist","dmeL","dmeR","vorL","vorR","vorLId","vorRId",
|
|
"range","status.wxr","status.wpt","hdgGroup","status.sta","status.arpt"])
|
|
me.symbols[element] = me.nd.getElementById(element);
|
|
|
|
# load elements from vector image, and create instance variables using identical names, and call updateCenter() on each
|
|
# anything that needs updatecenter called, should be added to the vector here
|
|
#
|
|
foreach(var element; ["windArrow","compassApp","northUp","aplSymMap","aplSymMapCtr","aplSymVor",
|
|
"staFromL2","staToL2","staFromR2","staToR2",
|
|
"locPtr","hdgTrk","truMag","altArc","planArcs",
|
|
"trkInd","compass","HdgBugCRT","TrkBugLCD","HdgBugLCD","selHdgLine","curHdgPtr",
|
|
"staFromL","staToL","staFromR","staToR"] )
|
|
me.symbols[element] = me.nd.getElementById(element).updateCenter();
|
|
|
|
foreach(var element; ["HdgBugCRT2","TrkBugLCD2","HdgBugLCD2","selHdgLine2","curHdgPtr2","vorCrsPtr2"] )
|
|
me.symbols[element] = me.nd.getElementById(element).setCenter(512,565);
|
|
|
|
# this should probably be using Philosopher's new SymbolLayer ?
|
|
me.map = me.nd.createChild("map","map")
|
|
.set("clip", "rect(124, 1024, 1024, 0)")
|
|
.set("screen-range", "700");
|
|
|
|
# this callback will be passed onto the model via the controller hash, and used for the positioned queries, to specify max query range:
|
|
var get_range = func me.get_switch('toggle_range');
|
|
|
|
# predicate for the draw controller
|
|
var is_tuned = func(freq) {
|
|
var nav1=getprop("instrumentation/nav[0]/frequencies/selected-mhz");
|
|
var nav2=getprop("instrumentation/nav[1]/frequencies/selected-mhz");
|
|
if (freq == nav1 or freq == nav2) return 1;
|
|
return 0;
|
|
}
|
|
|
|
# another predicate for the draw controller
|
|
var get_course_by_freq = func(freq) {
|
|
if (freq == getprop("instrumentation/nav[0]/frequencies/selected-mhz"))
|
|
return getprop("instrumentation/nav[0]/radials/selected-deg");
|
|
else
|
|
return getprop("instrumentation/nav[1]/radials/selected-deg");
|
|
}
|
|
|
|
var get_current_position = func {
|
|
return [
|
|
me.aircraft_source.get_lat(), me.aircraft_source.get_lon()
|
|
];
|
|
}
|
|
|
|
# a hash with controller callbacks, will be passed onto draw routines to customize behavior/appearance
|
|
# the point being that draw routines don't know anything about their frontends (instrument or GUI dialog)
|
|
# so we need some simple way to communicate between frontend<->backend until we have real controllers
|
|
# for now, a single controller hash is shared by most layers - unsupported callbacks are simply ignored by the draw files
|
|
#
|
|
var controller = {
|
|
query_range: func get_range(),
|
|
is_tuned:is_tuned,
|
|
get_tuned_course:get_course_by_freq,
|
|
get_position: get_current_position,
|
|
};
|
|
|
|
# FIXME: MapStructure: big hack
|
|
canvas.Symbol.Controller.get("VOR").query_range = controller.query_range;
|
|
canvas.Symbol.Controller.get("VOR").get_tuned_course = controller.get_tuned_course;
|
|
canvas.Symbol.Controller.get("DME").is_tuned = controller.is_tuned;
|
|
|
|
###
|
|
# set up various layers, controlled via callbacks in the controller hash
|
|
# revisit this code once Philosopher's "Smart MVC Symbols/Layers" work is committed and integrated
|
|
|
|
# helper / closure generator
|
|
var make_event_handler = func(predicate, layer) func predicate(me, layer);
|
|
|
|
me.layers={}; # storage container for all ND specific layers
|
|
# look up all required layers as specified per the NDStyle hash and do the initial setup for event handling
|
|
foreach(var layer; me.nd_style.layers) {
|
|
if(layer['disabled']) continue; # skip this layer
|
|
#print("newMFD(): Setting up ND layer:", layer.name);
|
|
# huge hack for the alt-arc, which is not rendered as a map group, but directly as part of the toplevel ND group
|
|
var render_target = (!contains(layer,'not_a_map') or !layer.not_a_map) ? me.map : me.nd;
|
|
|
|
var the_layer = nil;
|
|
if(!layer['isMapStructure'])
|
|
the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller );
|
|
else {
|
|
printlog(_MP_dbg_lvl, "Setting up MapStructure-based layer for ND, name:", layer.name);
|
|
render_target.addLayer(factory: canvas.SymbolLayer, type_arg: layer.name);
|
|
the_layer = me.layers[layer.name] = render_target.getLayer(layer.name);
|
|
}
|
|
|
|
# now register all layer specific notification listeners and their corresponding update predicate/callback
|
|
# pass the ND instance and the layer handle to the predicate when it is called
|
|
# so that it can directly access the ND instance and its own layer (without having to know the layer's name)
|
|
var event_handler = make_event_handler(layer.predicate, the_layer);
|
|
foreach(var event; layer.update_on) {
|
|
# print("Setting up subscription:", event, " for ", layer.name, " handler id:", id(event_handler) );
|
|
me.listen_switch(event, event_handler);
|
|
} # foreach event subscription
|
|
# and now update/init each layer once by calling its update predicate for initialization
|
|
event_handler();
|
|
} # foreach layer
|
|
|
|
#print("navdisplay.mfd:ND layer setup completed");
|
|
|
|
# start the update timer, which makes sure that the update() will be called
|
|
me.update_timer.start();
|
|
|
|
|
|
# next, radio & autopilot & listeners
|
|
# TODO: move this to .init field in layers hash or to model files
|
|
foreach(var n; var radios = [
|
|
"instrumentation/nav/frequencies/selected-mhz",
|
|
"instrumentation/nav[1]/frequencies/selected-mhz"])
|
|
me.listen(n, func() {
|
|
# me.drawvor();
|
|
# me.drawdme();
|
|
});
|
|
# TODO: move this to the route.model
|
|
# Hack to draw the route on rm activation
|
|
me.listen("/autopilot/route-manager/active", func(active) {
|
|
if(active.getValue()) {
|
|
me.drawroute();
|
|
me.drawrunways();
|
|
} else {
|
|
#print("TODO: navdisplay.mfd: implement route-manager/layer clearing!");
|
|
#me.route_group.removeAllChildren(); # HACK!
|
|
}
|
|
});
|
|
me.listen("/autopilot/route-manager/current-wp", func(activeWp) {
|
|
canvas.updatewp( activeWp.getValue() );
|
|
});
|
|
|
|
},
|
|
drawroute: func print("drawroute no longer used!"),
|
|
drawrunways: func print("drawrunways no longer used!"),
|
|
|
|
in_mode:func(switch, modes)
|
|
{
|
|
foreach(var m; modes) if(me.get_switch(switch)==m) return 1;
|
|
return 0;
|
|
},
|
|
# each model should keep track of when it last got updated, using current lat/lon
|
|
# in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range)
|
|
# and update each model accordingly
|
|
update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft
|
|
{
|
|
##
|
|
# important constants
|
|
var m1 = 111132.92;
|
|
var m2 = -559.82;
|
|
var m3 = 1.175;
|
|
var m4 = -0.0023;
|
|
var p1 = 111412.84;
|
|
var p2 = -93.5;
|
|
var p3 = 0.118;
|
|
var latNm = 60;
|
|
var lonNm = 60;
|
|
|
|
# fgcommand('profiler-start');
|
|
# Heading update
|
|
var userHdgMag = me.aircraft_source.get_hdg_mag();
|
|
var userHdgTru = me.aircraft_source.get_hdg_tru();
|
|
var userTrkMag = me.aircraft_source.get_trk_mag();
|
|
var userTrkTru = me.aircraft_source.get_trk_tru();
|
|
if(me.get_switch('toggle_true_north')) {
|
|
me.symbols.truMag.setText("TRU");
|
|
var userHdg=userHdgTru;
|
|
var userTrk=userTrkTru;
|
|
} else {
|
|
me.symbols.truMag.setText("MAG");
|
|
var userHdg=userHdgMag;
|
|
var userTrk=userTrkMag;
|
|
}
|
|
if (me.aircraft_source.get_spd() < 80)
|
|
userTrk = userHdg;
|
|
var userLat = me.aircraft_source.get_lat();
|
|
var userLon = me.aircraft_source.get_lon();
|
|
var userSpd = me.aircraft_source.get_spd();
|
|
var userVSpd = me.aircraft_source.get_vspd();
|
|
var dispLCD = me.get_switch('toggle_display_type') == "LCD";
|
|
|
|
# this should only ever happen when testing the experimental AI/MP ND driver hash (not critical)
|
|
if (!userHdg or !userTrk or !userLat or !userLon) {
|
|
print("aircraft source invalid, returning !");
|
|
return;
|
|
}
|
|
|
|
if(me.in_mode('toggle_display_mode', ['PLAN']))
|
|
me.map.setTranslation(512,512);
|
|
elsif(me.get_switch('toggle_centered'))
|
|
me.map.setTranslation(512,565);
|
|
else
|
|
me.map.setTranslation(512,824);
|
|
# Calculate length in NM of one degree at current location TODO: expose as methods, for external callbacks
|
|
var userLatR = userLat*D2R;
|
|
var userLonR = userLon*D2R;
|
|
var latlen = m1 + (m2 * math.cos(2 * userLatR)) + (m3 * math.cos(4 * userLatR)) + (m4 * math.cos(6 * userLatR));
|
|
var lonlen = (p1 * math.cos(userLatR)) + (p2 * math.cos(3 * userLatR)) + (p3 * math.cos(5 * userLatR));
|
|
latNm = latlen*M2NM; #60 at equator
|
|
lonNm = lonlen*M2NM; #60 at equator
|
|
|
|
me.symbols.wind.setText(sprintf("%3.0f / %2.0f",getprop("/environment/wind-from-heading-deg"),getprop("/environment/wind-speed-kt")));
|
|
|
|
if(me.get_switch('toggle_lh_vor_adf') == 1)
|
|
{
|
|
me.symbols.vorL.setText("VOR L");
|
|
me.symbols.vorL.setColor(0.195,0.96,0.097);
|
|
me.symbols.dmeL.setText("DME");
|
|
me.symbols.dmeL.setColor(0.195,0.96,0.097);
|
|
if(getprop("instrumentation/nav/in-range"))
|
|
me.symbols.vorLId.setText(getprop("instrumentation/nav/nav-id"));
|
|
else
|
|
me.symbols.vorLId.setText(getprop("instrumentation/nav/frequencies/selected-mhz-fmt"));
|
|
me.symbols.vorLId.setColor(0.195,0.96,0.097);
|
|
if(getprop("instrumentation/nav/dme-in-range"))
|
|
me.symbols.dmeLDist.setText(sprintf("%3.1f",getprop("instrumentation/nav/nav-distance")*0.000539));
|
|
else me.symbols.dmeLDist.setText(" ---");
|
|
me.symbols.dmeLDist.setColor(0.195,0.96,0.097);
|
|
} elsif(me.get_switch('toggle_lh_vor_adf') == -1) {
|
|
me.symbols.vorL.setText("ADF L");
|
|
me.symbols.vorL.setColor(0,0.6,0.85);
|
|
me.symbols.dmeL.setText("");
|
|
me.symbols.dmeL.setColor(0,0.6,0.85);
|
|
if((var navident=getprop("instrumentation/adf/ident")) != "")
|
|
me.symbols.vorLId.setText(navident);
|
|
else me.symbols.vorLId.setText(sprintf("%3d",getprop("instrumentation/adf/frequencies/selected-khz")));
|
|
me.symbols.vorLId.setColor(0,0.6,0.85);
|
|
me.symbols.dmeLDist.setText("");
|
|
me.symbols.dmeLDist.setColor(0,0.6,0.85);
|
|
} else {
|
|
me.symbols.vorL.setText("");
|
|
me.symbols.dmeL.setText("");
|
|
me.symbols.vorLId.setText("");
|
|
me.symbols.dmeLDist.setText("");
|
|
}
|
|
if(me.get_switch('toggle_rh_vor_adf') == 1) {
|
|
me.symbols.vorR.setText("VOR R");
|
|
me.symbols.vorR.setColor(0.195,0.96,0.097);
|
|
me.symbols.dmeR.setText("DME");
|
|
me.symbols.dmeR.setColor(0.195,0.96,0.097);
|
|
if(getprop("instrumentation/nav[1]/in-range"))
|
|
me.symbols.vorRId.setText(getprop("instrumentation/nav[1]/nav-id"));
|
|
else
|
|
me.symbols.vorRId.setText(getprop("instrumentation/nav[1]/frequencies/selected-mhz-fmt"));
|
|
me.symbols.vorRId.setColor(0.195,0.96,0.097);
|
|
if(getprop("instrumentation/nav[1]/dme-in-range"))
|
|
me.symbols.dmeRDist.setText(sprintf("%3.1f",getprop("instrumentation/nav[1]/nav-distance")*0.000539));
|
|
else me.symbols.dmeRDist.setText(" ---");
|
|
me.symbols.dmeRDist.setColor(0.195,0.96,0.097);
|
|
} elsif(me.get_switch('toggle_rh_vor_adf') == -1) {
|
|
me.symbols.vorR.setText("ADF R");
|
|
me.symbols.vorR.setColor(0,0.6,0.85);
|
|
me.symbols.dmeR.setText("");
|
|
me.symbols.dmeR.setColor(0,0.6,0.85);
|
|
if((var navident=getprop("instrumentation/adf[1]/ident")) != "")
|
|
me.symbols.vorRId.setText(navident);
|
|
else me.symbols.vorRId.setText(sprintf("%3d",getprop("instrumentation/adf[1]/frequencies/selected-khz")));
|
|
me.symbols.vorRId.setColor(0,0.6,0.85);
|
|
me.symbols.dmeRDist.setText("");
|
|
me.symbols.dmeRDist.setColor(0,0.6,0.85);
|
|
} else {
|
|
me.symbols.vorR.setText("");
|
|
me.symbols.dmeR.setText("");
|
|
me.symbols.vorRId.setText("");
|
|
me.symbols.dmeRDist.setText("");
|
|
}
|
|
|
|
me.symbols.range.setText(sprintf("%3.0f",me.rangeNm()/2));
|
|
|
|
# reposition the map, change heading & range:
|
|
if(me.in_mode('toggle_display_mode', ['PLAN'])) {
|
|
me.symbols.windArrow.setVisible(!dispLCD);
|
|
me.map._node.getNode("hdg",1).setDoubleValue(0);
|
|
if (getprop(me.efis_path ~ "/inputs/plan-wpt-index") >= 0) {
|
|
me.map._node.getNode("ref-lat",1).setDoubleValue(getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/latitude-deg"));
|
|
me.map._node.getNode("ref-lon",1).setDoubleValue(getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/longitude-deg"));
|
|
}
|
|
} else {
|
|
me.symbols.windArrow.show();
|
|
me.map._node.getNode("ref-lat",1).setDoubleValue(userLat);
|
|
me.map._node.getNode("ref-lon",1).setDoubleValue(userLon);
|
|
}
|
|
# The set range of the map does not correspond to what we see in-sim!!
|
|
me.map._node.getNode("range",1).setDoubleValue(me.rangeNm()); # avoid this here, use a listener instead
|
|
|
|
# Hide heading bug 10 secs after change
|
|
var vhdg_bug = getprop("autopilot/settings/heading-bug-deg");
|
|
var hdg_bug_active = getprop("autopilot/settings/heading-bug-active");
|
|
if (hdg_bug_active == nil)
|
|
hdg_bug_active = 1;
|
|
|
|
if(me.in_mode('toggle_display_mode', ['MAP'])) {
|
|
me.symbols.HdgBugCRT.setRotation((vhdg_bug-userTrk)*D2R);
|
|
me.symbols.HdgBugLCD.setRotation((vhdg_bug-userTrk)*D2R);
|
|
me.symbols.TrkBugLCD.setRotation((vhdg_bug-userTrk)*D2R);
|
|
me.symbols.selHdgLine.setRotation((vhdg_bug-userTrk)*D2R);
|
|
me.symbols.HdgBugCRT2.setRotation((vhdg_bug-userTrk)*D2R);
|
|
me.symbols.TrkBugLCD2.setRotation((vhdg_bug-userTrk)*D2R);
|
|
me.symbols.selHdgLine2.setRotation((vhdg_bug-userTrk)*D2R);
|
|
me.symbols.trkInd.setRotation(0);
|
|
me.symbols.curHdgPtr.setRotation((userHdg-userTrk)*D2R);
|
|
me.symbols.curHdgPtr2.setRotation((userHdg-userTrk)*D2R);
|
|
me.map._node.getNode("hdg",1).setDoubleValue(userTrkTru);
|
|
me.symbols.compass.setRotation(-userTrk*D2R);
|
|
me.symbols.compassApp.setRotation(-userTrk*D2R);
|
|
me.symbols.hdgTrk.setText("TRK");
|
|
me.symbols.windArrow.setRotation((getprop("/environment/wind-from-heading-deg")-userTrk)*D2R);
|
|
}
|
|
if(me.in_mode('toggle_display_mode', ['APP','VOR'])) {
|
|
me.symbols.HdgBugCRT.setRotation((vhdg_bug-userHdg)*D2R);
|
|
me.symbols.HdgBugLCD.setRotation((vhdg_bug-userHdg)*D2R);
|
|
me.symbols.selHdgLine.setRotation((vhdg_bug-userHdg)*D2R);
|
|
me.symbols.HdgBugCRT2.setRotation((vhdg_bug-userHdg)*D2R);
|
|
me.symbols.HdgBugLCD2.setRotation((vhdg_bug-userHdg)*D2R);
|
|
me.symbols.selHdgLine2.setRotation((vhdg_bug-userHdg)*D2R);
|
|
me.symbols.trkInd.setRotation((userTrk-userHdg)*D2R);
|
|
me.symbols.curHdgPtr.setRotation(0);
|
|
me.symbols.curHdgPtr2.setRotation(0);
|
|
me.map._node.getNode("hdg",1).setDoubleValue(userHdgTru);
|
|
me.symbols.compass.setRotation(-userHdg*D2R);
|
|
me.symbols.compassApp.setRotation(-userHdg*D2R);
|
|
me.symbols.hdgTrk.setText("HDG");
|
|
me.symbols.windArrow.setRotation((getprop("/environment/wind-from-heading-deg")-userHdg)*D2R);
|
|
}
|
|
if(me.get_switch('toggle_centered')) {
|
|
if (me.in_mode('toggle_display_mode', ['APP','VOR'])) {
|
|
me.symbols.vorCrsPtr2.show();
|
|
me.symbols.compassApp.show();
|
|
if(getprop("instrumentation/nav/in-range")) {
|
|
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
|
|
me.symbols.locPtr.show();
|
|
me.symbols.locPtr.setTranslation(deflection*150,0);
|
|
if(abs(deflection < 0.99))
|
|
me.symbols.locPtr.setColorFill(1,0,1,1);
|
|
else
|
|
me.symbols.locPtr.setColorFill(1,0,1,0);
|
|
} else {
|
|
me.symbols.locPtr.hide();
|
|
}
|
|
me.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-userHdg)*D2R);
|
|
me.symbols.hdgGroup.setTranslation(0,100);
|
|
} else {
|
|
me.symbols.vorCrsPtr2.hide();
|
|
me.symbols.hdgGroup.setTranslation(0,100*me.in_mode('toggle_display_mode', ['MAP']));
|
|
me.symbols.compassApp.setVisible(me.in_mode('toggle_display_mode', ['MAP']));
|
|
}
|
|
} else {
|
|
me.symbols.vorCrsPtr2.hide();
|
|
me.symbols.hdgGroup.setTranslation(0,0);
|
|
me.symbols.compassApp.hide();
|
|
}
|
|
|
|
if ((me.get_switch('toggle_centered') and !me.in_mode('toggle_display_mode', ['PLAN'])) or me.in_mode('toggle_display_mode', ['PLAN'])) {
|
|
me.symbols.compass.hide();
|
|
} else {
|
|
me.symbols.compass.show();
|
|
}
|
|
|
|
var staPtrVis = !me.in_mode('toggle_display_mode', ['APP','PLAN']);
|
|
var magVar = getprop("environment/magnetic-variation-deg");
|
|
if(me.in_mode('toggle_display_mode', ['APP','MAP','VOR','PLAN']))
|
|
{
|
|
if(getprop("instrumentation/nav/heading-deg") != nil)
|
|
var nav0hdg=getprop("instrumentation/nav/heading-deg") - userHdg - magVar;
|
|
if(getprop("instrumentation/nav[1]/heading-deg") != nil)
|
|
var nav1hdg=getprop("instrumentation/nav[1]/heading-deg") - userHdg - magVar;
|
|
var adf0hdg=getprop("instrumentation/adf/indicated-bearing-deg");
|
|
var adf1hdg=getprop("instrumentation/adf[1]/indicated-bearing-deg");
|
|
if(!me.get_switch('toggle_centered'))
|
|
{
|
|
if(me.in_mode('toggle_display_mode', ['PLAN']))
|
|
me.symbols.trkInd.hide();
|
|
else
|
|
me.symbols.trkInd.show();
|
|
if((getprop("instrumentation/nav/in-range") and me.get_switch('toggle_lh_vor_adf') == 1)) {
|
|
me.symbols.staFromL.setVisible(staPtrVis);
|
|
me.symbols.staToL.setVisible(staPtrVis);
|
|
me.symbols.staFromL.setColor(0.195,0.96,0.097);
|
|
me.symbols.staToL.setColor(0.195,0.96,0.097);
|
|
me.symbols.staFromL.setRotation((nav0hdg+180)*D2R);
|
|
me.symbols.staToL.setRotation(nav0hdg*D2R);
|
|
}
|
|
elsif(getprop("instrumentation/adf/in-range") and (me.get_switch('toggle_lh_vor_adf') == -1)) {
|
|
me.symbols.staFromL.setVisible(staPtrVis);
|
|
me.symbols.staToL.setVisible(staPtrVis);
|
|
me.symbols.staFromL.setColor(0,0.6,0.85);
|
|
me.symbols.staToL.setColor(0,0.6,0.85);
|
|
me.symbols.staFromL.setRotation((adf0hdg+180)*D2R);
|
|
me.symbols.staToL.setRotation(adf0hdg*D2R);
|
|
} else {
|
|
me.symbols.staFromL.hide();
|
|
me.symbols.staToL.hide();
|
|
}
|
|
if((getprop("instrumentation/nav[1]/in-range") and me.get_switch('toggle_rh_vor_adf') == 1)) {
|
|
me.symbols.staFromR.setVisible(staPtrVis);
|
|
me.symbols.staToR.setVisible(staPtrVis);
|
|
me.symbols.staFromR.setColor(0.195,0.96,0.097);
|
|
me.symbols.staToR.setColor(0.195,0.96,0.097);
|
|
me.symbols.staFromR.setRotation((nav1hdg+180)*D2R);
|
|
me.symbols.staToR.setRotation(nav1hdg*D2R);
|
|
} elsif(getprop("instrumentation/adf[1]/in-range") and (me.get_switch('toggle_rh_vor_adf') == -1)) {
|
|
me.symbols.staFromR.setVisible(staPtrVis);
|
|
me.symbols.staToR.setVisible(staPtrVis);
|
|
me.symbols.staFromR.setColor(0,0.6,0.85);
|
|
me.symbols.staToR.setColor(0,0.6,0.85);
|
|
me.symbols.staFromR.setRotation((adf1hdg+180)*D2R);
|
|
me.symbols.staToR.setRotation(adf1hdg*D2R);
|
|
} else {
|
|
me.symbols.staFromR.hide();
|
|
me.symbols.staToR.hide();
|
|
}
|
|
me.symbols.staFromL2.hide();
|
|
me.symbols.staToL2.hide();
|
|
me.symbols.staFromR2.hide();
|
|
me.symbols.staToR2.hide();
|
|
me.symbols.curHdgPtr2.hide();
|
|
me.symbols.HdgBugCRT2.hide();
|
|
me.symbols.TrkBugLCD2.hide();
|
|
me.symbols.HdgBugLCD2.hide();
|
|
me.symbols.selHdgLine2.hide();
|
|
me.symbols.curHdgPtr.setVisible(staPtrVis);
|
|
me.symbols.TrkBugLCD.hide();
|
|
me.symbols.HdgBugCRT.setVisible(staPtrVis and !dispLCD);
|
|
me.symbols.HdgBugLCD.setVisible(staPtrVis and dispLCD);
|
|
me.symbols.selHdgLine.setVisible(staPtrVis and hdg_bug_active);
|
|
} else {
|
|
me.symbols.trkInd.hide();
|
|
if((getprop("instrumentation/nav/in-range") and me.get_switch('toggle_lh_vor_adf') == 1)) {
|
|
me.symbols.staFromL2.setVisible(staPtrVis);
|
|
me.symbols.staToL2.setVisible(staPtrVis);
|
|
me.symbols.staFromL2.setColor(0.195,0.96,0.097);
|
|
me.symbols.staToL2.setColor(0.195,0.96,0.097);
|
|
me.symbols.staFromL2.setRotation((nav0hdg+180)*D2R);
|
|
me.symbols.staToL2.setRotation(nav0hdg*D2R);
|
|
} elsif(getprop("instrumentation/adf/in-range") and (me.get_switch('toggle_lh_vor_adf') == -1)) {
|
|
me.symbols.staFromL2.setVisible(staPtrVis);
|
|
me.symbols.staToL2.setVisible(staPtrVis);
|
|
me.symbols.staFromL2.setColor(0,0.6,0.85);
|
|
me.symbols.staToL2.setColor(0,0.6,0.85);
|
|
me.symbols.staFromL2.setRotation((adf0hdg+180)*D2R);
|
|
me.symbols.staToL2.setRotation(adf0hdg*D2R);
|
|
} else {
|
|
me.symbols.staFromL2.hide();
|
|
me.symbols.staToL2.hide();
|
|
}
|
|
if((getprop("instrumentation/nav[1]/in-range") and me.get_switch('toggle_rh_vor_adf') == 1)) {
|
|
me.symbols.staFromR2.setVisible(staPtrVis);
|
|
me.symbols.staToR2.setVisible(staPtrVis);
|
|
me.symbols.staFromR2.setColor(0.195,0.96,0.097);
|
|
me.symbols.staToR2.setColor(0.195,0.96,0.097);
|
|
me.symbols.staFromR2.setRotation((nav1hdg+180)*D2R);
|
|
me.symbols.staToR2.setRotation(nav1hdg*D2R);
|
|
} elsif(getprop("instrumentation/adf[1]/in-range") and (me.get_switch('toggle_rh_vor_adf') == -1)) {
|
|
me.symbols.staFromR2.setVisible(staPtrVis);
|
|
me.symbols.staToR2.setVisible(staPtrVis);
|
|
me.symbols.staFromR2.setColor(0,0.6,0.85);
|
|
me.symbols.staToR2.setColor(0,0.6,0.85);
|
|
me.symbols.staFromR2.setRotation((adf1hdg+180)*D2R);
|
|
me.symbols.staToR2.setRotation(adf1hdg*D2R);
|
|
} else {
|
|
me.symbols.staFromR2.hide();
|
|
me.symbols.staToR2.hide();
|
|
}
|
|
me.symbols.staFromL.hide();
|
|
me.symbols.staToL.hide();
|
|
me.symbols.staFromR.hide();
|
|
me.symbols.staToR.hide();
|
|
me.symbols.curHdgPtr.hide();
|
|
me.symbols.HdgBugCRT.hide();
|
|
me.symbols.TrkBugLCD.hide();
|
|
me.symbols.HdgBugLCD.hide();
|
|
me.symbols.selHdgLine.hide();
|
|
me.symbols.curHdgPtr2.setVisible(staPtrVis);
|
|
me.symbols.TrkBugLCD2.hide();
|
|
me.symbols.HdgBugCRT2.setVisible(staPtrVis and !dispLCD);
|
|
me.symbols.HdgBugLCD2.setVisible(staPtrVis and dispLCD);
|
|
me.symbols.selHdgLine2.setVisible(staPtrVis and hdg_bug_active);
|
|
}
|
|
}
|
|
|
|
me.symbols.hdgGroup.setVisible(!me.in_mode('toggle_display_mode', ['PLAN']));
|
|
me.symbols.northUp.setVisible(me.in_mode('toggle_display_mode', ['PLAN']));
|
|
me.symbols.aplSymMap.setVisible(me.in_mode('toggle_display_mode', ['APP','MAP','VOR']) and !me.get_switch('toggle_centered'));
|
|
me.symbols.aplSymMapCtr.setVisible(me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_centered'));
|
|
me.symbols.aplSymVor.setVisible(me.in_mode('toggle_display_mode', ['APP','VOR']) and me.get_switch('toggle_centered'));
|
|
me.symbols.planArcs.setVisible(me.in_mode('toggle_display_mode', ['PLAN']));
|
|
|
|
if (abs(userVSpd) > 5) {
|
|
var altDiff = (getprop("autopilot/settings/target-altitude-ft") or 0)-(getprop("instrumentation/altimeter/indicated-altitude-ft") or 0);
|
|
if (abs(altDiff) > 50 and altDiff/userVSpd > 0) {
|
|
var altRangeNm = altDiff/userVSpd*userSpd*KT2MPS*M2NM;
|
|
if(altRangeNm > 1) {
|
|
var altRangePx = (350/me.rangeNm())*altRangeNm;
|
|
if (altRangePx > 700)
|
|
altRangePx = 700;
|
|
me.symbols.altArc.setTranslation(0,-altRangePx);
|
|
}
|
|
me.symbols.altArc.show();
|
|
} else
|
|
me.symbols.altArc.hide();
|
|
} else {
|
|
me.symbols.altArc.hide();
|
|
}
|
|
|
|
## run all predicates in the NDStyle hash and evaluate their true/false behavior callbacks
|
|
## this is in line with the original design, but normally we don't need to getprop/poll here,
|
|
## using listeners or timers would be more canvas-friendly whenever possible
|
|
## because running setprop() on any group/canvas element at framerate means that the canvas
|
|
## will be updated at frame rate too - wasteful ... (check the performance monitor!)
|
|
|
|
foreach(var feature; me.nd_style.features ) {
|
|
# for stuff that always needs to be updated
|
|
if (contains(feature.impl, 'common')) feature.impl.common(me);
|
|
# conditional stuff
|
|
if(!contains(feature.impl, 'predicate')) continue; # no conditional stuff
|
|
if ( var result=feature.impl.predicate(me) )
|
|
feature.impl.is_true(me, result); # pass the result to the predicate
|
|
else
|
|
feature.impl.is_false( me, result ); # pass the result to the predicate
|
|
}
|
|
|
|
## update the status flags shown on the ND (wxr, wpt, arpt, sta)
|
|
# this could/should be using listeners instead ...
|
|
me.symbols['status.wxr'].setVisible( me.get_switch('toggle_weather') and me.in_mode('toggle_display_mode', ['MAP']));
|
|
me.symbols['status.wpt'].setVisible( me.get_switch('toggle_waypoints') and me.in_mode('toggle_display_mode', ['MAP']));
|
|
me.symbols['status.arpt'].setVisible( me.get_switch('toggle_airports') and me.in_mode('toggle_display_mode', ['MAP']));
|
|
me.symbols['status.sta'].setVisible( me.get_switch('toggle_stations') and me.in_mode('toggle_display_mode', ['MAP']));
|
|
}
|
|
};
|