diff --git a/Aircraft/Instruments-3d/radardist/radardist.nas b/Aircraft/Instruments-3d/radardist/radardist.nas index 6e2e0b5be..910f2e307 100644 --- a/Aircraft/Instruments-3d/radardist/radardist.nas +++ b/Aircraft/Instruments-3d/radardist/radardist.nas @@ -45,159 +45,159 @@ var FT2M = 0.3048; var NM2KM = 1.852; var my_maxrange = func(a) { - max_range = 0; - radar_range = 0; - radar_area = 0; - acname = aircraftData[a] or 0; - if ( acname ) { - have_radar = radarData[acname][4]; - if ( have_radar != "none" and have_radar != "unknown") { - radar_area = radarData[acname][7]; - radar_range = radarData[acname][5]; - if ( radar_area > 0 ) { max_range = radar_range / radar_area } - } - } - return( max_range ); + max_range = 0; + radar_range = 0; + radar_area = 0; + acname = aircraftData[a] or 0; + if ( acname ) { + have_radar = radarData[acname][4]; + if ( have_radar != "none" and have_radar != "unknown") { + radar_area = radarData[acname][7]; + radar_range = radarData[acname][5]; + if ( radar_area > 0 ) { max_range = radar_range / radar_area } + } + } + return( max_range ); } var get_ecm_type_num = func(a) { - acname = aircraftData[a] or 0; - var num = 0; - if ( acname ) { - num = radarData[acname][8]; - } - return( num ); + acname = aircraftData[a] or 0; + var num = 0; + if ( acname ) { + num = radarData[acname][8]; + } + return( num ); } var get_aircraft_name = func( t ) { - # Get the multiplayer aircraft name. - mpnode_string = t; - mpnode = props.globals.getNode(mpnode_string); - if ( find("tanker", mpnode_string) > 0 ) { - cutname = "KC135"; - } else { - mpname_node_string = mpnode_string ~ "/sim/model/path"; - mpname_node = props.globals.getNode(mpname_node_string); - if (mpname_node == nil) { return(0) } + # Get the multiplayer aircraft name. + mpnode_string = t; + mpnode = props.globals.getNode(mpnode_string); + if ( find("tanker", mpnode_string) > 0 ) { + cutname = "KC135"; + } else { + mpname_node_string = mpnode_string ~ "/sim/model/path"; + mpname_node = props.globals.getNode(mpname_node_string); + if (mpname_node == nil) { return(0) } - var mpname = mpname_node.getValue(); - if (mpname == nil) { return(0) } + var mpname = mpname_node.getValue(); + if (mpname == nil) { return(0) } - splitname = split("/", mpname); - cutname = splitname[1]; - - } - return( cutname ); + splitname = split("/", mpname); + # + # cutname = splitname[1]; + # + # **** by 5H1N0B1 05/01/2014 + # Fixed a problem onboard radar happens automatically when you are in range of an mp gamer that uses "OpenRadar" + # + cutname = splitname[size(splitname)-1]; + + } + return( cutname ); } var radis = func(t, my_radarcorr) { - cutname = get_aircraft_name(t); - # Calculate the rcs detection range, - # if aircraft is not found in list, 0 (generic) will be used. - acname = aircraftData[cutname]; - if ( acname == nil ) { acname = 0 } - rcs_4r = radarData[acname][3]; + cutname = get_aircraft_name(t); + # Calculate the rcs detection range, + # if aircraft is not found in list, 0 (generic) will be used. + acname = aircraftData[cutname]; + if ( acname == nil ) { acname = 0 } + rcs_4r = radarData[acname][3]; - # Add a correction factor for altitude, as lower alt means - # shorter radar distance (due to air turbulence). - alt_corr = 1; - alt_ac = mpnode.getNode("position/altitude-ft").getValue(); - if (alt_ac <= 1000) { - alt_corr = 0.6; - } elsif ((alt_ac > 1000) and (alt_ac <= 5000)) { - alt_corr = 0.8; - } - # Add a correction factor for altitude AGL. Skip if AI tanker. - agl_corr = 1; - if ( find("tanker", t) == 0 ) { - mp_lon = mpnode.getNode("position/longitude-deg").getValue(); - pos_elev = geo.elevation(mp_lat, mp_lon); - if (pos_elev != nil) { - mp_agl = alt_ac - ( pos_elev / FT2M ); - if (mp_agl <= 40) { - agl_corr = 0.03; - } elsif ((mp_agl > 40) and (mp_agl <= 80)) { - agl_corr = 0.07; - } elsif ((mp_agl > 80) and (mp_agl <= 120)) { - agl_corr = 0.25; - } elsif ((mp_agl > 120) and (mp_agl <= 300)) { - agl_corr = 0.4; - } elsif ((mp_agl > 300) and (mp_agl <= 600)) { - agl_corr = 0.7; - } elsif ((mp_agl > 600) and (mp_agl <= 1000)) { - agl_corr = 0.85; - } - } - } - # Calculate the detection distance for this multiplayer. - det_range = my_radarcorr * rcs_4r * alt_corr * agl_corr / NM2KM; + # Add a correction factor for altitude, as lower alt means + # shorter radar distance (due to air turbulence). + alt_corr = 1; + alt_ac = mpnode.getNode("position/altitude-ft").getValue(); + if (alt_ac <= 1000) { + alt_corr = 0.6; + } elsif ((alt_ac > 1000) and (alt_ac <= 5000)) { + alt_corr = 0.8; + } + # Add a correction factor for altitude AGL. Skip if AI tanker. + agl_corr = 1; + if ( find("tanker", t) == 0 ) { + mp_lon = mpnode.getNode("position/longitude-deg").getValue(); + pos_elev = geo.elevation(mp_lat, mp_lon); + if (pos_elev != nil) { + mp_agl = alt_ac - ( pos_elev / FT2M ); + if (mp_agl <= 40) { + agl_corr = 0.03; + } elsif ((mp_agl > 40) and (mp_agl <= 80)) { + agl_corr = 0.07; + } elsif ((mp_agl > 80) and (mp_agl <= 120)) { + agl_corr = 0.25; + } elsif ((mp_agl > 120) and (mp_agl <= 300)) { + agl_corr = 0.4; + } elsif ((mp_agl > 300) and (mp_agl <= 600)) { + agl_corr = 0.7; + } elsif ((mp_agl > 600) and (mp_agl <= 1000)) { + agl_corr = 0.85; + } + } + } + # Calculate the detection distance for this multiplayer. + det_range = my_radarcorr * rcs_4r * alt_corr * agl_corr / NM2KM; - # Compare if aircraft is in detection range and return. - act_range = mpnode.getNode("radar/range-nm").getValue() or 500; - if (det_range >= act_range) { - return(1); - } - return(0); + # Compare if aircraft is in detection range and return. + act_range = mpnode.getNode("radar/range-nm").getValue() or 500; + if (det_range >= act_range) { + return(1); + } + return(0); } var radar_horizon = func(our_alt_ft, target_alt_ft) { - if (our_alt_ft < 0 or our_alt_ft == nil) { our_alt_ft = 0 } - if (target_alt_ft < 0 or target_alt_ft == nil) { target_alt_ft = 0 } - return( 2.2 * ( math.sqrt(our_alt_ft * FT2M) + math.sqrt(target_alt_ft * FT2M) ) ); + if (our_alt_ft < 0 or our_alt_ft == nil) { our_alt_ft = 0 } + if (target_alt_ft < 0 or target_alt_ft == nil) { target_alt_ft = 0 } + return( 2.2 * ( math.sqrt(our_alt_ft * FT2M) + math.sqrt(target_alt_ft * FT2M) ) ); } var load_data = func { - # a) converts aircraft model name to lookup (index) number in aircraftData{}. - # b) appends ordered list of data into radarData[], - # data is: - # - acname (the index number) - # - the first (if several) aircraft model name corresponding to this type, - # - RCS(m2), - # - 4th root of RCS, - # - radar type, - # - max. radar range(km), - # - max. radar range target seize(RCS)m2, - # - 4th root of radar RCS. - var data_node = props.globals.getNode("instrumentation/radar-performance/data"); - var aircraft_types = data_node.getChildren(); - foreach( var t; aircraft_types ) { - var index = t.getIndex(); - var aircraft_names = t.getChildren(); - foreach( var n; aircraft_names) { - if ( n.getName() == "name") { - aircraftData[n.getValue()] = index; - } - } - var t_list = [ - index, - t.getNode("name[0]").getValue(), - t.getNode("rcs-sq-meter").getValue(), - t.getNode("rcs-4th-root").getValue(), - t.getNode("radar-type").getValue(), - t.getNode("max-radar-rng-km").getValue(), - t.getNode("max-target-sq-meter").getValue(), - t.getNode("max-target-4th-root").getValue(), - t.getNode("ecm-type-num").getValue() - ]; - append(radarData, t_list); - } + # a) converts aircraft model name to lookup (index) number in aircraftData{}. + # b) appends ordered list of data into radarData[], + # data is: + # - acname (the index number) + # - the first (if several) aircraft model name corresponding to this type, + # - RCS(m2), + # - 4th root of RCS, + # - radar type, + # - max. radar range(km), + # - max. radar range target seize(RCS)m2, + # - 4th root of radar RCS. + var data_node = props.globals.getNode("instrumentation/radar-performance/data"); + var aircraft_types = data_node.getChildren(); + foreach( var t; aircraft_types ) { + var index = t.getIndex(); + var aircraft_names = t.getChildren(); + foreach( var n; aircraft_names) { + if ( n.getName() == "name") { + aircraftData[n.getValue()] = index; + } + } + var t_list = [ + index, + t.getNode("name[0]").getValue(), + t.getNode("rcs-sq-meter").getValue(), + t.getNode("rcs-4th-root").getValue(), + t.getNode("radar-type").getValue(), + t.getNode("max-radar-rng-km").getValue(), + t.getNode("max-target-sq-meter").getValue(), + t.getNode("max-target-4th-root").getValue(), + t.getNode("ecm-type-num").getValue() + ]; + append(radarData, t_list); + } } var launched = 0; var init = func { - if (! launched) { - print("Initializing Radar Data"); - io.read_properties(data_path, props.globals); - load_data(); - launched = 1; - } + if (! launched) { + print("Initializing Radar Data"); + io.read_properties(data_path, props.globals); + load_data(); + launched = 1; + } } - - - - - - diff --git a/Input/Joysticks/Genius/f31.xml b/Input/Joysticks/Genius/f31.xml index 783594e88..30f3862f1 100644 --- a/Input/Joysticks/Genius/f31.xml +++ b/Input/Joysticks/Genius/f31.xml @@ -1,28 +1,36 @@ - + Padix Co. Ltd. 10-Button USB Joystick @@ -44,7 +52,7 @@ Buttons: property-scale /controls/flight/elevator 2 - -1.0 + -1.0 @@ -58,7 +66,7 @@ Buttons: property-scale /controls/flight/rudder 2 - -1.0 + -1.0 @@ -75,7 +83,7 @@ Buttons: - View Direction + View direction 4 6 @@ -85,7 +93,7 @@ Buttons: true + 3.0 @@ -145,28 +153,37 @@ Buttons: property-adjust /sim/current-view/goal-pitch-offset-deg - -3.0 + -3.0 - - - + + + diff --git a/Input/Joysticks/Logitech/g940.xml b/Input/Joysticks/Logitech/g940.xml new file mode 100644 index 000000000..a29bc2409 --- /dev/null +++ b/Input/Joysticks/Logitech/g940.xml @@ -0,0 +1,661 @@ + + + + + G940 + + + + + Aileron + + property-scale + /controls/flight/aileron + + + + + Elevator + + property-scale + /controls/flight/elevator + -1.0 + + + + + Aileron trim + + property-scale + /controls/flight/aileron-trim + + + + + + + View direction + + 12 + + + View left + true + + + nasal + + + + + View right + true + + + nasal + + + + + + + View elevation + + 13 + + + View down + true + + property-adjust + /sim/current-view/goal-pitch-offset-deg + 3.0 + + + + View up + true + + property-adjust + /sim/current-view/goal-pitch-offset-deg + -3.0 + + + + + + + + + + + + + + + + + + + + + + Throttle right engine + + 6 + + + + nasal + + + + + + + + Throttle left engine + + 7 + + + property-scale + /controls/engines/engine[0]/throttle + -1.0 + -0.5 + + + + + Mixture + + 9 + + + property-scale + /controls/engines/engine/mixture + -1.0 + -0.5 + + + + + Propeller pitch + + 8 + + + property-scale + /controls/engines/engine/propeller-pitch + -1.0 + -0.5 + + + + + View zoom + + 14 + + + Zoom out + true + + nasal + + + + + Zoom in + true + + nasal + + + + + + + Reset zoom + + 15 + + + Reset zoom to default + false + + property-assign + /sim/current-view/field-of-view + /sim/view/config/default-field-of-view-deg + + + + Reset zoom for greater overview + false + + nasal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rudder + + 2 + + + property-scale + /controls/flight/rudder + 2 + + + + + + 4 + + Left differential break + + property-scale + /controls/gear/brake-left + -1.0 + -0.5 + + + + + + 3 + + Right differential break + + property-scale + /controls/gear/brake-right + -1.0 + -0.5 + + + + + diff --git a/Nasal/canvas/MapStructure.nas b/Nasal/canvas/MapStructure.nas index 5474e9145..2ec29eb80 100644 --- a/Nasal/canvas/MapStructure.nas +++ b/Nasal/canvas/MapStructure.nas @@ -1,3 +1,123 @@ +var dump_obj = func(m) { + var h = {}; + foreach (var k; keys(m)) + if (k != "parents") + h[k] = m[k]; + debug.dump(h); +}; + +## +# must be either of: +# 1) draw* callback, 2) SVG filename, 3) Drawable class (with styling/LOD support) +var SymbolDrawable = { + new: func() { + }, +}; + +## wrapper for each element +## i.e. keeps the canvas and texture map coordinates +var CachedElement = { + new: func(canvas_path, name, source, offset) { + var m = {parents:[CachedElement] }; + m.canvas_src = canvas_path; + m.name = name; + m.source = source; + m.offset = offset; + return m; + }, # new() + render: func(group) { + # create a raster image child in the render target/group + return + group.createChild("image", me.name) + .setFile( me.canvas_src ) + # TODO: fix .setSourceRect() to accept a single vector for coordinates ... + .setSourceRect(left:me.source[0],top:me.source[1],right:me.source[2],bottom:me.source[3] , normalized:0) + .setTranslation(me.offset); # FIXME: make sure this stays like this and isn't overridden + }, # render() +}; # of CachedElement + +var SymbolCache = { + # We can draw symbols either with left/top, centered, + # or right/bottom alignment. Specify two in a vector + # to mix and match, e.g. left/centered would be + # [SymbolCache.DRAW_LEFT_TOP,SymbolCache.DRAW_CENTERED] + DRAW_LEFT_TOP: 0.0, + DRAW_CENTERED: 0.5, + DRAW_RIGHT_BOTTOM: 1.0, + new: func(dim...) { + var m = { parents:[SymbolCache] }; + # to keep track of the next free caching spot (in px) + m.next_free = [0, 0]; + # to store each type of symbol + m.dict = {}; + if (size(dim) == 1 and typeof(dim[0]) == 'vector') + dim = dim[0]; + # Two sizes: canvas and symbol + if (size(dim) == 2) { + var canvas_x = var canvas_y = dim[0]; + var image_x = var image_y = dim[1]; + # Two widths (canvas and symbol) and then height/width ratio + } else if (size(dim) == 3) { + var (canvas_x,image_x,ratio) = dim; + var canvas_y = canvas_x * ratio; + var image_y = image_x * ratio; + # Explicit canvas and symbol widths/heights + } else if (size(dim) == 4) { + var (canvas_x,canvas_y,image_x,image_y) = dim; + } + m.canvas_sz = [canvas_x, canvas_y]; + m.image_sz = [image_x, image_y]; + + # allocate a canvas + m.canvas_texture = canvas.new( { + "name": "SymbolCache"~canvas_x~'x'~canvas_y, + "size": m.canvas_sz, + "view": m.canvas_sz, + "mipmapping": 1 + }); + + # add a placement + m.canvas_texture.addPlacement( {"type": "ref"} ); + + return m; + }, + add: func(name, callback, draw_mode=0) { + if (typeof(draw_mode) == 'scalar') + var draw_mode0 = var draw_mode1 = draw_mode; + else var (draw_mode0,draw_mode1) = draw_mode; + # get canvas texture that we use as cache + # get next free spot in texture (column/row) + # run the draw callback and render into a group + var gr = me.canvas_texture.createGroup(); + gr.setTranslation( me.next_free[0] + me.image_sz[0]*draw_mode0, + me.next_free[1] + me.image_sz[1]*draw_mode1); + #settimer(func debug.dump ( gr.getTransformedBounds() ), 0); # XXX: these are only updated when rendered + #debug.dump ( gr.getTransformedBounds() ); + gr.update(); # apparently this doesn't result in sane output from .getTransformedBounds() either + #debug.dump ( gr.getTransformedBounds() ); + # draw the symbol + callback(gr); + # get the bounding box, i.e. coordinates for texture map, or use the .setTranslation() params + var coords = me.next_free~me.next_free; + foreach (var i; [0,1]) + coords[i+2] += me.image_sz[i]; + # get the offset we used to position correctly in the bounds of the canvas + var offset = [me.image_sz[0]*draw_mode0, me.image_sz[1]*draw_mode1]; + # store texture map coordinates in lookup map using the name as identifier + me.dict[name] = CachedElement.new(me.canvas_texture.getPath(), name, coords, offset ); + # update next free position in cache (column/row) + me.next_free[0] += me.image_sz[0]; + if (me.next_free[0] >= me.canvas_sz[0]) + { me.next_free[0] = 0; me.next_free[1] += me.image_sz[1] } + if (me.next_free[1] >= me.canvas_sz[1]) + die("SymbolCache: ran out of space after adding '"~name~"'"); + }, # add() + get: func(name) { + if(!contains(me.dict,name)) die("No SymbolCache entry for key:"~ name); + return me.dict[name]; + }, # get() +}; + var Symbol = { # Static/singleton: registry: {}, @@ -29,7 +149,7 @@ Symbol.Controller = { # Static/singleton: registry: {}, add: func(type, class) - registry[type] = class, + me.registry[type] = class, get: func(type) if ((var class = me.registry[type]) == nil) die("unknown type '"~type~"'"); @@ -58,13 +178,17 @@ var getpos_fromghost = func(positioned_g) # (geo.Coord and positioned ghost currently) Symbol.Controller.getpos = func(obj) { if (typeof(obj) == 'ghost') - if (ghosttype(obj) == 'positioned' or ghosttype(obj) == 'Navaid') + if (ghosttype(obj) == 'positioned' or ghosttype(obj) == 'Navaid' or ghosttype(obj)=='Fix' or ghosttype(obj)=='flightplan-leg') return getpos_fromghost(obj); else die("bad ghost of type '"~ghosttype(obj)~"'"); if (typeof(obj) == 'hash') if (isa(obj, geo.Coord)) return obj.latlon(); + if (contains(obj,'lat') and contains(obj,'lon')) + return [obj.lat, obj.lon]; + + debug.dump(obj); die("no suitable getpos() found! Of type: "~typeof(obj)); }; @@ -82,38 +206,17 @@ var DotSym = { element_id: nil, # Static/singleton: makeinstance: func(name, hash) { - assert_ms(hash, - "element_type", # type of Canvas element - #"element_id", # optional Canvas id - #"init", # initialize routine - "draw", # init/update routine - #getpos", # get position from model in [x_units,y_units] (optional) - ); - hash.parents = [DotSym]; + if (!isa(hash, DotSym)) + die("OOP error"); + #assert_ms(hash, + # "element_type", # type of Canvas element + # #"element_id", # optional Canvas id + # #"init", # initialize routine + # "draw", # init/update routine + # #getpos", # get position from model in [x_units,y_units] (optional) + #); return Symbol.add(name, hash); }, - readinstance: func(file, name=nil) { - #print(file); - if (name == nil) - var name = split("/", file)[-1]; - if (substr(name, size(name)-4) == ".draw") - name = substr(name, 0, size(name)-5); - var code = io.readfile(file); - var code = call(compile, [code], var err=[]); - if (size(err)) { - if (substr(err[0], 0, 12) == "Parse error:") { # hack around Nasal feature - var e = split(" at line ", err[0]); - if (size(e) == 2) - err[0] = string.join("", [e[0], "\n at ", file, ", line ", e[1], "\n "]); - } - for (var i = 1; (var c = caller(i)) != nil; i += 1) - err ~= subvec(c, 2, 2); - debug.printerror(err); - return; - } - call(code, nil, nil, var hash = { parents:[DotSym] }); - me.makeinstance(name, hash); - }, # For the instances returned from makeinstance: # @param group The Canvas group to add this to. # @param model A correct object (e.g. positioned ghost) as @@ -132,6 +235,10 @@ var DotSym = { ), }; if (m.controller != nil) { + #print("Creating controller"); + temp = m.controller.new(m.model,m); + if (temp != nil) + m.controller = temp; #print("Initializing controller"); m.controller.init(model); } @@ -281,93 +388,117 @@ SymbolLayer.Controller = { die("searchCmd() not implemented for this SymbolLayer.Controller type!"), }; # of SymbolLayer.Controller -settimer(func { -Map.Controller = { -# Static/singleton: - registry: {}, - add: func(type, class) - me.registry[type] = class, - get: func(type) - if ((var class = me.registry[type]) == nil) - die("unknown type '"~type~"'"); - else return class, - # Calls corresonding controller constructor - # @param map The #SymbolMap this controller is responsible for. - new: func(type, layer, arg...) - return call((var class = me.get(type)).new, [map]~arg, class), +var AnimatedLayer = { }; -####### LOAD FILES ####### -#print("loading files"); -(func { - var FG_ROOT = getprop("/sim/fg-root"); - var load = func(file, name) { - #print(file); - if (name == nil) - var name = split("/", file)[-1]; - if (substr(name, size(name)-4) == ".draw") - name = substr(name, 0, size(name)-5); - #print("reading file"); - var code = io.readfile(file); - #print("compiling file"); - # This segfaults for some reason: - #var code = call(compile, [code], var err=[]); - var code = call(func compile(code, file), [code], var err=[]); - if (size(err)) { - #print("handling error"); - if (substr(err[0], 0, 12) == "Parse error:") { # hack around Nasal feature - var e = split(" at line ", err[0]); - if (size(e) == 2) - err[0] = string.join("", [e[0], "\n at ", file, ", line ", e[1], "\n "]); +var CompassLayer = { +}; + +var AltitudeArcLayer = { +}; + +load_MapStructure = func { + Map.Controller = { + # Static/singleton: + registry: {}, + add: func(type, class) + me.registry[type] = class, + get: func(type) + if ((var class = me.registry[type]) == nil) + die("unknown type '"~type~"'"); + else return class, + # Calls corresonding controller constructor + # @param map The #SymbolMap this controller is responsible for. + new: func(type, layer, arg...) + return call((var class = me.get(type)).new, [map]~arg, class), + }; + + ####### LOAD FILES ####### + #print("loading files"); + (func { + var FG_ROOT = getprop("/sim/fg-root"); + var load = func(file, name) { + #print(file); + if (name == nil) + var name = split("/", file)[-1]; + if (substr(name, size(name)-4) == ".draw") + name = substr(name, 0, size(name)-5); + #print("reading file"); + var code = io.readfile(file); + #print("compiling file"); + # This segfaults for some reason: + #var code = call(compile, [code], var err=[]); + var code = call(func compile(code, file), [code], var err=[]); + if (size(err)) { + #print("handling error"); + if (substr(err[0], 0, 12) == "Parse error:") { # hack around Nasal feature + var e = split(" at line ", err[0]); + if (size(e) == 2) + err[0] = string.join("", [e[0], "\n at ", file, ", line ", e[1], "\n "]); + } + for (var i = 1; (var c = caller(i)) != nil; i += 1) + err ~= subvec(c, 2, 2); + debug.printerror(err); + return; } - for (var i = 1; (var c = caller(i)) != nil; i += 1) - err ~= subvec(c, 2, 2); - debug.printerror(err); - return; + #print("calling code"); + call(code, nil, nil, var hash = {}); + #debug.dump(keys(hash)); + return hash; + }; + + var load_deps = func(name) { + load(FG_ROOT~"/Nasal/canvas/map/"~name~".lcontroller", name); + load(FG_ROOT~"/Nasal/canvas/map/"~name~".symbol", name); + load(FG_ROOT~"/Nasal/canvas/map/"~name~".scontroller", name); } - #print("calling code"); - call(code, nil, nil, var hash = {}); - #debug.dump(keys(hash)); - return hash; - }; - load(FG_ROOT~"/Nasal/canvas/map/VOR.lcontroller", "VOR"); - DotSym.readinstance(FG_ROOT~"/Nasal/canvas/map/VOR.symbol", "VOR"); - load(FG_ROOT~"/Nasal/canvas/map/VOR.scontroller", "VOR"); - load(FG_ROOT~"/Nasal/canvas/map/aircraftpos.controller", "VOR"); -})(); -#print("finished loading files"); -####### TEST SYMBOL ####### -if (0) -settimer(func { - if (caller(0)[0] != globals.canvas) - return call(caller(0)[1], arg, nil, globals.canvas); + foreach( var name; ['VOR','FIX','NDB','DME','WPT'] ) + load_deps( name ); + load(FG_ROOT~"/Nasal/canvas/map/aircraftpos.controller", name); - print("Running MapStructure test code"); - var TestCanvas = canvas.new({ - "name": "Map Test", - "size": [1024, 1024], - "view": [1024, 1024], - "mipmapping": 1 - }); - var dlg = canvas.Window.new([400, 400], "dialog"); - dlg.setCanvas(TestCanvas); - var TestMap = TestCanvas.createGroup().createChild("map"); # we should not directly use a canvas here, but instead a LayeredMap.new() - TestMap.addLayer(factory: SymbolLayer, type_arg: "VOR"); # the ID should be also exposed in the property tree for each group (layer), i.e. better debugging - # Center the map's origin: - TestMap.setTranslation(512,512); # FIXME: don't hardcode these values, but read in canvas texture dimensions, otherwise it will break once someone uses non 1024x1024 textures ... - # Initialize a range (TODO: LayeredMap.Controller): - TestMap.set("range", 100); - # Little cursor of current position: - TestMap.createChild("path").rect(-5,-5,10,10).setColorFill(1,1,1).setColor(0,1,0); - # And make it move with our aircraft: - TestMap.setController("Aircraft position"); # from aircraftpos.controller - dlg.del = func() { - TestMap.del(); - # call inherited 'del' - delete(me, "del"); - me.del(); - }; -}, 1); -}, 0); # end ugly module init timer hack + ### + # set up a cache for 32x32 symbols + var SymbolCache32x32 = SymbolCache.new(1024,32); + var drawVOR = func(color, width=3) return func(group) { + # print("drawing vor"); + var bbox = group.createChild("path") + .moveTo(-15,0) + .lineTo(-7.5,12.5) + .lineTo(7.5,12.5) + .lineTo(15,0) + .lineTo(7.5,-12.5) + .lineTo(-7.5,-12.5) + .close() + .setStrokeLineWidth(width) + .setColor( color ); + # debug.dump( bbox.getBoundingBox() ); + }; + + var cachedVOR1 = SymbolCache32x32.add( "VOR-BLUE", drawVOR( color:[0, 0.6, 0.85], width:3), SymbolCache.DRAW_CENTERED ); + var cachedVOR2 = SymbolCache32x32.add( "VOR-RED" , drawVOR( color:[1.0, 0, 0], width: 3), SymbolCache.DRAW_CENTERED ); + var cachedVOR3 = SymbolCache32x32.add( "VOR-GREEN" , drawVOR( color:[0, 1, 0], width: 3), SymbolCache.DRAW_CENTERED ); + var cachedVOR4 = SymbolCache32x32.add( "VOR-WHITE" , drawVOR( color:[1, 1, 1], width: 3), SymbolCache.DRAW_CENTERED ); + + # STRESS TEST + if (0) { + for(var i=0;i <= 1024/32*4 - 4; i+=1) + SymbolCache32x32.add( "VOR-YELLOW"~i , drawVOR( color:[1, 1, 0], width: 3) ); + + var dlg = canvas.Window.new([640,320],"dialog"); + var my_canvas = dlg.createCanvas().setColorBackground(1,1,1,1); + var root = my_canvas.createGroup(); + + SymbolCache32x32.get(name:"VOR-BLUE").render( group: root ).setGeoPosition(getprop("/position/latitude-deg"),getprop("/position/longitude-deg")); + } + + })(); + #print("finished loading files"); + ####### TEST SYMBOL ####### + + canvas.load_MapStructure = func; + +}; # load_MapStructure + +setlistener("/nasal/canvas/loaded", load_MapStructure); # end ugly module init listener hack diff --git a/Nasal/canvas/api.nas b/Nasal/canvas/api.nas index 8cccc71f8..bd42c061d 100644 --- a/Nasal/canvas/api.nas +++ b/Nasal/canvas/api.nas @@ -462,47 +462,45 @@ var Map = { addLayer: func(factory, type_arg=nil, priority=nil) { if (!contains(me, "layers")) - me.layers = []; + me.layers = {}; + + if(contains(me.layers, type_arg)) + print("addLayer() warning: overwriting existing layer:", type_arg); + + # print("addLayer():", type_arg); # Argument handling if (type_arg != nil) var type = factory.get(type_arg); else var type = factory; + me.layers[type_arg]= type.new(me); if (priority == nil) priority = type.df_priority; - append(me.layers, [type.new(me), priority]); if (priority != nil) - me._sort_priority(); + me.layers[type_arg].setInt("z-index", priority); return me; }, - setPos: func(lat,lon,hdg=nil) + getLayer: func(type_arg) me.layers[type_arg], + setPos: func(lat, lon, hdg=nil, range=nil) { me.set("ref-lat", lat); me.set("ref-lon", lon); if (hdg != nil) me.set("hdg", hdg); - - # me.map.set("range", 100); + if (range != nil) + me.set("range", range); }, # Update each layer on this Map. Called by # me.controller. update: func { - foreach (var l; me.layers) - call(l[0].update, arg, l[0]); + foreach (var l; keys(me.layers)) { + var layer = me.layers[l]; + call(layer.update, arg, layer); + } return me; }, -# private: - _sort_priority: func() - { - me.layers = sort(me.layers, me._sort_cmp); - forindex (var i; me.layers) - me.layers[i].set("z-index", i); - }, - _sort_cmp: func(a,b) { - a[1] != b[1] and a[1] != nil and b[1] != nil and (a[1] < b[1] ? -1 : 1) - }, }; # Text diff --git a/Nasal/canvas/map.nas b/Nasal/canvas/map.nas index 1b91fe76b..187619913 100644 --- a/Nasal/canvas/map.nas +++ b/Nasal/canvas/map.nas @@ -429,10 +429,16 @@ var files_with = func(ext) { } return results; } -foreach(var ext; var extensions = ['.draw','.model','.layer']) - load_modules(files_with(ext)); +setlistener("/nasal/canvas/loaded", func { + foreach(var ext; var extensions = ['.draw','.model','.layer']) + load_modules(files_with(ext)); + + if (contains(canvas,"load_MapStructure")) + load_MapStructure(); + + # canvas.MFD = {EFIS:}; # where we'll be storing all MFDs + # TODO: should be inside a separate subfolder, i.e. canvas/map/mfd + load_modules( files_with('.mfd'), 'canvas' ); +}); -# canvas.MFD = {EFIS:}; # where we'll be storing all MFDs -# TODO: should be inside a separate subfolder, i.e. canvas/map/mfd -load_modules( files_with('.mfd'), 'canvas' ); diff --git a/Nasal/canvas/map/DME.lcontroller b/Nasal/canvas/map/DME.lcontroller new file mode 100644 index 000000000..b2d3c714e --- /dev/null +++ b/Nasal/canvas/map/DME.lcontroller @@ -0,0 +1,32 @@ +# Class things: +var name = 'DME'; +var parents = [SymbolLayer.Controller]; +var __self__ = caller(0)[0]; +SymbolLayer.Controller.add(name, __self__); +SymbolLayer.add(name, { + parents: [SymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one +}); +var a_instance = nil; +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + listeners: [], + query_range_nm: 25, + query_type:'dme', + }; + __self__.a_instance = m; + return m; +}; +var del = func() { + foreach (var l; me.listeners) + removelistener(l); +}; + +var searchCmd = func { + #print("Running query:", me.query_type); + return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch +}; + diff --git a/Nasal/canvas/map/DME.scontroller b/Nasal/canvas/map/DME.scontroller new file mode 100644 index 000000000..0e54d4dc2 --- /dev/null +++ b/Nasal/canvas/map/DME.scontroller @@ -0,0 +1,12 @@ +# Class things: +var name = 'DME'; +var parents = [Symbol.Controller]; +var __self__ = caller(0)[0]; +Symbol.Controller.add(name, __self__); +Symbol.registry[ name ].df_controller = __self__; +var new = func(model) ; # this controller doesn't need an instance +var LayerController = SymbolLayer.Controller.registry[ name ]; +var isActive = func(model) LayerController.a_instance.isActive(model); +var is_tuned = func() + die( name~".scontroller.is_tuned /MUST/ be provided by implementation" ); + diff --git a/Nasal/canvas/map/DME.symbol b/Nasal/canvas/map/DME.symbol new file mode 100644 index 000000000..fbf523227 --- /dev/null +++ b/Nasal/canvas/map/DME.symbol @@ -0,0 +1,35 @@ +# Class things: +var name = 'DME'; +var parents = [DotSym]; +var __self__ = caller(0)[0]; +DotSym.makeinstance( name, __self__ ); + +var element_type = "group"; # we want a group, becomes "me.element" +var icon_dme = nil; + +var draw = func { + # Init + if (me.icon_dme == nil) { + me.icon_dme = me.element.createChild("path") + .moveTo(-15,0) + .line(-12.5,-7.5) + .line(7.5,-12.5) + .line(12.5,7.5) + .lineTo(7.5,-12.5) + .line(12.5,-7.5) + .line(7.5,12.5) + .line(-12.5,7.5) + .lineTo(15,0) + .lineTo(7.5,12.5) + .vert(14.5) + .horiz(-14.5) + .vert(-14.5) + .close() + .setStrokeLineWidth(3); + } + if (me.controller != nil and me.controller.is_tuned(me.model.frequency/100)) + me.icon_dme.setColor(0,1,0); + else + me.icon_dme.setColor(0,0.6,0.85); +}; + diff --git a/Nasal/canvas/map/FIX.lcontroller b/Nasal/canvas/map/FIX.lcontroller new file mode 100644 index 000000000..ed89fb06b --- /dev/null +++ b/Nasal/canvas/map/FIX.lcontroller @@ -0,0 +1,33 @@ +# Class things: +var name = 'FIX'; +var parents = [SymbolLayer.Controller]; +var __self__ = caller(0)[0]; +SymbolLayer.Controller.add(name, __self__); +SymbolLayer.add(name, { + parents: [SymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one +}); +var a_instance = nil; +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + listeners: [], + query_range_nm: 25, + query_type:'fix', + }; + __self__.a_instance = m; + return m; +}; +var del = func() { + #print("VOR.lcontroller.del()"); + foreach (var l; me.listeners) + removelistener(l); +}; + +var searchCmd = func { + #print("Running query:", me.query_type); + return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch +}; + diff --git a/Nasal/canvas/map/FIX.scontroller b/Nasal/canvas/map/FIX.scontroller new file mode 100644 index 000000000..5726f86ab --- /dev/null +++ b/Nasal/canvas/map/FIX.scontroller @@ -0,0 +1,12 @@ +# Class things: +var name = 'FIX'; +var parents = [Symbol.Controller]; +var __self__ = caller(0)[0]; +Symbol.Controller.add(name, __self__); +Symbol.registry[ name ].df_controller = __self__; +var new = func(model) ; # this controller doesn't need an instance +var LayerController = SymbolLayer.Controller.registry[ name ]; +var isActive = func(model) LayerController.a_instance.isActive(model); +var query_range = func() + die( name~".scontroller.query_range /MUST/ be provided by implementation" ); + diff --git a/Nasal/canvas/map/FIX.symbol b/Nasal/canvas/map/FIX.symbol new file mode 100644 index 000000000..c2eee2c11 --- /dev/null +++ b/Nasal/canvas/map/FIX.symbol @@ -0,0 +1,30 @@ +# Class things: +var name = 'FIX'; +var parents = [DotSym]; +var __self__ = caller(0)[0]; +DotSym.makeinstance( name, __self__ ); + +var element_type = "group"; # we want a group, becomes "me.element" +var icon_fix = nil; +var text_fix = nil; + +var draw = func { + if (me.icon_fix != nil) return; + # the fix symbol + me.icon_fix = me.element.createChild("path") + .moveTo(-15,15) + .lineTo(0,-15) + .lineTo(15,15) + .close() + .setStrokeLineWidth(3) + .setColor(0,0.6,0.85) + .setScale(0.5,0.5); # FIXME: do proper LOD handling here - we need to scale according to current texture dimensions vs. original/design dimensions + # the fix label + me.text_fix = me.element.createChild("text") + .setDrawMode( canvas.Text.TEXT ) + .setText(me.model.id) + .setFont("LiberationFonts/LiberationSans-Regular.ttf") + .setFontSize(28) + .setTranslation(5,25); +}; + diff --git a/Nasal/canvas/map/NDB.lcontroller b/Nasal/canvas/map/NDB.lcontroller new file mode 100644 index 000000000..3250fc0f7 --- /dev/null +++ b/Nasal/canvas/map/NDB.lcontroller @@ -0,0 +1,30 @@ +# Class things: +var name = 'NDB'; +var parents = [SymbolLayer.Controller]; +var __self__ = caller(0)[0]; +SymbolLayer.Controller.add(name, __self__); +SymbolLayer.add(name, { + parents: [SymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one +}); +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + listeners: [], + query_range_nm: 25, + query_type:'ndb', + }; + return m; +}; +var del = func() { + foreach (var l; me.listeners) + removelistener(l); +}; + +var searchCmd = func { + #print("Running query:", me.query_type); + return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch +}; + diff --git a/Nasal/canvas/map/NDB.scontroller b/Nasal/canvas/map/NDB.scontroller new file mode 100644 index 000000000..7d1011db0 --- /dev/null +++ b/Nasal/canvas/map/NDB.scontroller @@ -0,0 +1,12 @@ +# Class things: +var name = 'NDB'; +var parents = [Symbol.Controller]; +var __self__ = caller(0)[0]; +Symbol.Controller.add(name, __self__); +Symbol.registry[ name ].df_controller = __self__; +var new = func(model) ; # this controller doesn't need an instance +var LayerController = SymbolLayer.Controller.registry[ name ]; +var isActive = func(model) LayerController.a_instance.isActive(model); +var query_range = func() + die( name~".scontroller.query_range /MUST/ be provided by implementation" ); + diff --git a/Nasal/canvas/map/NDB.symbol b/Nasal/canvas/map/NDB.symbol new file mode 100644 index 000000000..541b4cebb --- /dev/null +++ b/Nasal/canvas/map/NDB.symbol @@ -0,0 +1,18 @@ +# Class things: +var name = 'NDB'; +var parents = [DotSym]; +var __self__ = caller(0)[0]; +DotSym.makeinstance( name, __self__ ); + +var element_type = "group"; # we want a group, becomes "me.element", which we parse a SVG onto +var svg_path = "/gui/dialogs/images/ndb_symbol.svg"; # speaking of path, this is our path to use +var local_svg_path = nil; # track changes in the SVG's path + +var draw = func { + if (me.svg_path == me.local_svg_path) return; + me.element.removeAllChildren(); + me.local_svg_path = me.svg_path; + canvas.parsesvg(me.element, me.svg_path); + me.inited = 1; +}; + diff --git a/Nasal/canvas/map/VOR.lcontroller b/Nasal/canvas/map/VOR.lcontroller index 1f63e4e67..f01f83da9 100644 --- a/Nasal/canvas/map/VOR.lcontroller +++ b/Nasal/canvas/map/VOR.lcontroller @@ -7,6 +7,7 @@ SymbolLayer.add("VOR", { type: "VOR", # Symbol type df_controller: __self__, # controller to use by default -- this one }); +var a_instance = nil; var new = func(layer) { var m = { parents: [__self__], @@ -14,6 +15,7 @@ var new = func(layer) { active_vors: [], navNs: props.globals.getNode("instrumentation").getChildren("nav"), listeners: [], + query_type:'vor', }; setsize(m.active_vors, size(m.navNs)); foreach (var navN; m.navNs) { @@ -24,6 +26,7 @@ var new = func(layer) { } #call(debug.dump, keys(layer)); m.changed_freq(update:0); + __self__.a_instance = m; return m; }; var del = func() { @@ -46,7 +49,7 @@ var changed_freq = func(update=1) { if (update) me.layer.update(); }; var searchCmd = func { - #print("Run query"); - return positioned.findWithinRange(100, 'vor'); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch + #print("Running query:", me.query_type); + return positioned.findWithinRange(100, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch }; diff --git a/Nasal/canvas/map/VOR.scontroller b/Nasal/canvas/map/VOR.scontroller index b95bcc4f2..65fe8f408 100644 --- a/Nasal/canvas/map/VOR.scontroller +++ b/Nasal/canvas/map/VOR.scontroller @@ -1,10 +1,11 @@ # Class things: var parents = [Symbol.Controller]; var __self__ = caller(0)[0]; +Symbol.Controller.add("VOR", __self__); Symbol.registry["VOR"].df_controller = __self__; var new = func(model) ; # this controller doesn't need an instance -var LayerController = SymbolLayer.registry["VOR"]; -var isActive = func(model) LayerController.isActive(model); +var LayerController = SymbolLayer.Controller.registry["VOR"]; +var isActive = func(model) LayerController.a_instance.isActive(model); var query_range = func() die("VOR.scontroller.query_range /MUST/ be provided by implementation"); diff --git a/Nasal/canvas/map/VOR.symbol b/Nasal/canvas/map/VOR.symbol index aaa259dcc..faf37c206 100644 --- a/Nasal/canvas/map/VOR.symbol +++ b/Nasal/canvas/map/VOR.symbol @@ -1,52 +1,57 @@ -# Read by the DotSym.readinstance; each variable becomes a derived class's member/method +# Class things: +var name = 'VOR'; +var parents = [DotSym]; +var __self__ = caller(0)[0]; +DotSym.makeinstance( name, __self__ ); var element_type = "group"; # we want a group, becomes "me.element" -var inited = 0; # this allows us to track whether draw() is an init() or an update() +var icon_vor = nil; var range_vor = nil; # two elements that get drawn when needed var radial_vor = nil; # if one is nil, the other has to be nil var draw = func { - if (me.inited) { - # Update - if (me.controller.isActive(me.model)) { - if (me.range_vor == nil) { - var rangeNm = me.controller.query_range(); - # print("VOR is tuned:", me.model.id); - var radius = (me.model.range_nm/rangeNm)*345; - me.range_vor = me.element.createChild("path") - .moveTo(-radius,0) - .arcSmallCW(radius,radius,0,2*radius,0) - .arcSmallCW(radius,radius,0,-2*radius,0) - .setStrokeLineWidth(3) - .setStrokeDashArray([5, 15, 5, 15, 5]) - .setColor(0,1,0); + # Init + if (me.icon_vor == nil) { + me.icon_vor = me.element.createChild("path") + .moveTo(-15,0) + .lineTo(-7.5,12.5) + .lineTo(7.5,12.5) + .lineTo(15,0) + .lineTo(7.5,-12.5) + .lineTo(-7.5,-12.5) + .close() + .setStrokeLineWidth(3) + .setColor(0,0.6,0.85); + } + # Update + if (me.controller.isActive(me.model)) { + if (me.range_vor == nil) { + var rangeNm = me.controller.query_range(); + # print("VOR is tuned:", me.model.id); + var radius = (me.model.range_nm/rangeNm)*345; + me.range_vor = me.element.createChild("path") + .moveTo(-radius,0) + .arcSmallCW(radius,radius,0,2*radius,0) + .arcSmallCW(radius,radius,0,-2*radius,0) + .setStrokeLineWidth(3) + .setStrokeDashArray([5, 15, 5, 15, 5]) + .setColor(0,1,0); - var course = controller.get_tuned_course(me.model.frequency/100); - vor_grp.createChild("path") - .moveTo(0,-radius) - .vert(2*radius) - .setStrokeLineWidth(3) - .setStrokeDashArray([15, 5, 15, 5, 15]) - .setColor(0,1,0) - .setRotation(course*D2R); - icon_vor.setColor(0,1,0); - } - me.range_vor.show(); - me.radial_vor.show(); - } else { - me.range_vor.hide(); - me.radial_vor.hide(); + var course = me.controller.get_tuned_course(me.model.frequency/100); + me.radial_vor = me.element.createChild("path") + .moveTo(0,-radius) + .vert(2*radius) + .setStrokeLineWidth(3) + .setStrokeDashArray([15, 5, 15, 5, 15]) + .setColor(0,1,0) + .setRotation(course*D2R); + me.icon_vor.setColor(0,1,0); } - } else # Init - me.element.createChild("path") - .moveTo(-15,0) - .lineTo(-7.5,12.5) - .lineTo(7.5,12.5) - .lineTo(15,0) - .lineTo(7.5,-12.5) - .lineTo(-7.5,-12.5) - .close() - .setStrokeLineWidth(3) - .setColor(0,0.6,0.85); + me.range_vor.show(); + me.radial_vor.show(); + } elsif (me.range_vor != nil) { + me.range_vor.hide(); + me.radial_vor.hide(); + } }; diff --git a/Nasal/canvas/map/WPT.lcontroller b/Nasal/canvas/map/WPT.lcontroller new file mode 100644 index 000000000..13bcc2f63 --- /dev/null +++ b/Nasal/canvas/map/WPT.lcontroller @@ -0,0 +1,39 @@ +# Class things: +var name = 'WPT'; # for waypoints +var parents = [SymbolLayer.Controller]; +var __self__ = caller(0)[0]; +SymbolLayer.Controller.add(name, __self__); +SymbolLayer.add(name, { + parents: [SymbolLayer], + type: name, # Symbol type + df_controller: __self__, # controller to use by default -- this one +}); +var new = func(layer) { + var m = { + parents: [__self__], + layer: layer, + listeners: [], + query_range_nm: 25, + query_type:'vor', + }; + return m; +}; +var del = func() { + #print("VOR.lcontroller.del()"); + foreach (var l; me.listeners) + removelistener(l); +}; + +var searchCmd = func { + #print("Running query: WPT"); + + var fp = flightplan(); + var fpSize = fp.getPlanSize(); + var result = []; + for (var i = 1; i 1890) # Only display suitably large airports - validApt = 1; - if (result.id == getprop("autopilot/route-manager/destination/airport") or result.id == getprop("autopilot/route-manager/departure/airport")) - validApt = 1; - } + var apt = airportinfo(result.id); + var runways = apt.runways; + var runway_keys = sort(keys(runways),string.icmp); + var validApt = 0; + foreach(var rwy; runway_keys){ + var r = runways[rwy]; + if (r.length > 1890) # Only display suitably large airports + validApt = 1; + if (result.id == getprop("autopilot/route-manager/destination/airport") or result.id == getprop("autopilot/route-manager/departure/airport")) + validApt = 1; + } - if(validApt) { - #canvas.draw_apt(me.apt_group, result.lat,result.lon,result.id,i); - me.push(result); - numResults += 1; - } + if(validApt) { + #canvas.draw_apt(me.apt_group, result.lat,result.lon,result.id,i); + me.push(result); + numResults += 1; } } # set RefPos and hdg to apt !! diff --git a/Nasal/canvas/map/boeingND.svg b/Nasal/canvas/map/boeingND.svg index 402c6dbaf..641be3035 100644 --- a/Nasal/canvas/map/boeingND.svg +++ b/Nasal/canvas/map/boeingND.svg @@ -17,548 +17,206 @@ height="1024" width="1024" version="1.1">image/svg+xmlGijs de Rooy - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -image/svg+xmlGijs de Rooy - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -30 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:39.99999619px">30 @@ -1634,10 +601,10 @@ id="text3088" y="-108.93858" x="-845.96948" - style="font-size:36.000011px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.00001144px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(0,-1,1,0,0,0)">24 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:39.99995041px">24 @@ -1728,10 +635,10 @@ id="text3096" y="-1071.5541" x="-879.19897" - style="font-size:36.000046px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.00004578px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(-0.866026,-0.5,0.5,-0.866026,0,0)">18 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:40.00000381px">18 @@ -1822,10 +669,10 @@ id="text3104" y="-1581.0712" x="-51.792171" - style="font-size:36.000004px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.00000381px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(-0.866025,0.5,-0.5,-0.866025,0,0)">12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:40.00001144px">12 @@ -1916,10 +703,10 @@ id="text3112" y="-1123.9312" x="812.80585" - style="font-size:36.000042px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.00004196px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(0,1,-1,0,0,0)">6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:39.99998093px">6 @@ -2010,10 +737,10 @@ id="text3120" y="-158.40652" x="844.79596" - style="font-size:36.000095px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.00009537px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(0.866026,0.5,-0.5,0.866026,0,0)"> - - - - - - - - - - - - - - - - - - -33 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:80.43321228px">33 @@ -2466,10 +957,10 @@ id="text3928" y="265.32059" x="-503.69513" - style="font-size:36.000031px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.00003052px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(0.5,-0.866026,0.866026,0.5,0,0)">27 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:80.43321228px">27 @@ -2560,10 +991,10 @@ id="text3936" y="-541.37213" x="-1019.1653" - style="font-size:35.999966px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:35.99996567px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(-0.5,-0.866025,0.866025,-0.5,0,0)">21 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:80.43321228px">21 @@ -2654,10 +1025,10 @@ id="text3944" y="-1392.2814" x="-557.84332" - style="font-size:36.000027px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.0000267px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="scale(-1,-1)">15 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:80.43321228px">15 @@ -2748,10 +1059,10 @@ id="text3952" y="-1431.3702" x="409.84222" - style="font-size:36.000061px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.00006104px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(-0.5,0.866026,-0.866026,-0.5,0,0)">9 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:80.43321228px">9 @@ -2842,10 +1093,10 @@ id="text3960" y="-611.09729" x="941.70276" - style="font-size:36.000008px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;display:inline;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" + style="font-size:36.00000763px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ededed;fill-opacity:1;stroke:none;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans" xml:space="preserve" transform="matrix(0.5,0.866025,-0.866025,0.5,0,0)">3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="font-size:80.43321228px">3 WXR + ARPT + WPT + STA + N + + W + + E + + S - \ No newline at end of file + + + \ No newline at end of file diff --git a/Nasal/canvas/map/dme.model b/Nasal/canvas/map/dme.model index 90853158d..53e4d0f61 100644 --- a/Nasal/canvas/map/dme.model +++ b/Nasal/canvas/map/dme.model @@ -11,12 +11,10 @@ DMEModel.init = func { )); } - var results = positioned.findWithinRange(me._controller.query_range()*2 ,"dme"); + var results = positioned.findWithinRange(me._controller.query_range() ,"dme"); foreach(result; results) { me.push(result); } me.notifyView(); -} - - +} \ No newline at end of file diff --git a/Nasal/canvas/map/fixes.model b/Nasal/canvas/map/fixes.model index 5d46eeee4..97e2697f9 100644 --- a/Nasal/canvas/map/fixes.model +++ b/Nasal/canvas/map/fixes.model @@ -4,10 +4,10 @@ FixModel.new = func make( LayerModel, FixModel ); FixModel.init = func { me._view.reset(); # wraps removeAllChildren() ATM - var results = positioned.findWithinRange( me._controller['query_range']()*2 ,"fix"); + var results = positioned.findWithinRange( me._controller['query_range']() ,"fix"); var numNum = 0; foreach(result; results) { - # Skip airport navaids (real thing makes distinction between high/low altitude fixes) + # Skip airport fixes if(string.match(result.id,"*[^0-9]")) { me.push(result); numNum = numNum + 1; diff --git a/Nasal/canvas/map/navaids.model b/Nasal/canvas/map/navaids.model index 65cf37344..e4edbf2d6 100644 --- a/Nasal/canvas/map/navaids.model +++ b/Nasal/canvas/map/navaids.model @@ -2,7 +2,7 @@ var NavaidModel = {}; NavaidModel.new = func make(LayerModel, NavaidModel); NavaidModel.init = func { me._view.reset(); - var navaids = findNavaidsWithinRange(15); + var navaids = findNavaidsWithinRange(me._controller.query_range()); foreach(var n; navaids) me.push(n); me.notifyView(); diff --git a/Nasal/canvas/map/navdisplay.mfd b/Nasal/canvas/map/navdisplay.mfd index f65dc9b87..f3fc85030 100644 --- a/Nasal/canvas/map/navdisplay.mfd +++ b/Nasal/canvas/map/navdisplay.mfd @@ -2,14 +2,10 @@ # 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; @@ -24,259 +20,319 @@ 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, +# any aircraft-specific ND behavior should be wrapped here, # to isolate/decouple things in the generic NavDisplay class # -# Note to Gijs: this may look weird and confusing, but it' actually requires -# less coding now, and it is now even possible to configure things via a little -# XML wrapper # TODO: move this to an XML config file -# +# var NDStyles = { - ## - # this configures the 744 ND to help generalize the NavDisplay class itself - 'B747-400': { - 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', update_on:['toggle_range','toggle_waypoints','toggle_display_mode'], predicate: func(nd, layer) { - # print("Running fixes predicate"); - var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']); - if(nd.rangeNm() <= 40 and visible) { - trigger_update( layer ); - } layer._view.setVisible(visible); - - }, # end of layer update predicate - }, # end of fixes 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) { - trigger_update( layer ); - } layer._view.setVisible(visible); - - }, # end of layer update predicate - }, # end of storms layer - - { name:'airplaneSymbol', update_on:['toggle_range','toggle_display_mode'], predicate: func(nd, layer) { - # print("Running fixes predicate"); - var visible=nd.get_switch('toggle_display_mode') == "PLAN"; - if (visible) { - 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 (nd.rangeNm() <= 80 and visible) { - trigger_update( layer ); # clear & redraw - } - layer._view.setVisible( visible); - - }, # end of layer update predicate - }, # end of airports layer - - { name:'vor', update_on:['toggle_range','toggle_stations','toggle_display_mode'], predicate: func(nd, layer) { - var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']); - if(nd.rangeNm() <= 40 and visible) { - trigger_update( layer ); # clear & redraw - } - layer._view.setVisible( visible ); - }, # end of layer update predicate - }, # end of VOR layer - - { name:'dme', update_on:['toggle_range','toggle_stations','toggle_display_mode'], predicate: func(nd, layer) { - var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']); - if(nd.rangeNm() <= 40 and visible){ - trigger_update( layer ); # clear & redraw - } - layer._view.setVisible( visible ); - }, # 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( 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_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(); + ## + # 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"; }, - 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.in_mode('toggle_display_mode', ['APP','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 + # 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) + ## - ], # end of vector with features + 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 - }, # end of 744 ND style + { 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") ) ; + 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) { + trigger_update( layer ); # clear & redraw + layer._view.setVisible( 1 ); #nd.get_switch('toggle_traffic') + }, # 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"), + 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 (737, 757, 777 etc) +## 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_trk_mag= func getprop("/orientation/track-magnetic-deg"); - m.get_trk_tru= func getprop("/orientation/track-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; +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 +# 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. @@ -284,18 +340,21 @@ return m; # TODO: switches are ND specific, so move to the NDStyle hash! var default_switches = { - 'toggle_range': {path: '/inputs/range-nm', value:10, 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/tfc',value:0, type:'BOOL'}, - 'toggle_centered': {path: '/inputs/nd-centered',value:0, type:'BOOL'}, - 'toggle_display_mode': {path: '/mfd/display-mode', value:'MAP', type:'STRING'}, - 'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'}, + '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 @@ -315,297 +374,297 @@ var update_apl_sym = func { update_apl_sym(); ## -# TODO: +# 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 listeners"); - # shut down all timers and other loops here - me.update_timer.stop(); - foreach(var l; me.listeners) - removelistener(l); - }, + # reset handler + handle_reinit: func { + print("Cleaning up NavDisplay listeners"); + # shut down all timers and other loops here + me.update_timer.stop(); + foreach(var l; me.listeners) + removelistener(l); + }, - listen: func(p,c) { - append(me.listeners, setlistener(p,c)); - }, + 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(); - }); + # 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 ~ + }, - # 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); - # 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 ); - }, + return getprop( path ); + }, - # for creating NDs that are driven by AI traffic instead of the main aircraft (generalization rocks!) - connectAI: func(source=nil) { + # 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_hdg_tru: func source.getNode('orientation/heading-deg').getValue(), get_trk_mag: func source.getNode('orientation/track-magnetic-deg').getValue(), - get_trk_tru: func source.getNode('orientation/track-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(), - get_vspd: func source.getNode('velocities/vertical-speed-fps').getValue(), }; - }, # of connectAI + }, # 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='B747-400') { - var m = { parents : [NavDisplay]}; + # 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.listeners=[]; # for cleanup handling - m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading) + 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.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.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 ; + 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'); + # 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"); + 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.mfd_display_mode = m.mfd.initNode("display-mode",m.mfd_mode_list[2],"STRING"); - m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL"); - m.previous_set = m.efis.initNode("inhg-previos",29.92); # watch out typo here, check other files before fixing ! - 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.nd_centered = m.efis.initNode("inputs/nd-centered",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: 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"); - # TODO: these are switches, can be unified with switch handling hash above (eventually): - - m.rh_vor_adf = m.efis.initNode("inputs/rh-vor-adf",0,"INT"); # not yet in switches hash - m.lh_vor_adf = m.efis.initNode("inputs/lh-vor-adf",0,"INT"); # not yet in switches hash - m.nd_plan_wpt = m.efis.initNode("inputs/plan-wpt-index", 0, "INT"); # ditto + 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"); - ### - # 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 - ); - + # 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 - return m; - }, + ### + # 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) { - me.listen("/sim/signals/reinit", func me.handle_reinit() ); - me.update_timer = maketimer(0.05, func me.update() ); # TODO: make interval configurable via ctor me.nd = canvas_group; - # 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}); + 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) + # 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","vorLId","vorRId", - "range","status.wxr","status.wpt", - "status.sta","status.arpt"]) - me.symbols[element] = me.nd.getElementById(element); + 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; ["rotateComp","rotateComp2","windArrow","selHdg","selHdg2","hdgGroup","northUp", - "aplSymMap","aplSymMapCtr","aplSymVor","curHdgPtr","curHdgPtr2", - "staFromL","staToL","staFromR","staToR","staFromL2","staToL2","staFromR2","staToR2", - "trkInd","vorCrsPtr2","locPtr","compass","compassApp","hdgTrk","truMag","altArc","planArcs"] ) - me.symbols[element] = me.nd.getElementById(element).updateCenter(); + # + 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)"); # 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; + 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"); + 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() - ]; + 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, - }; - + # + 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) { - 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 = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller ); + 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; - # 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 + 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 { + #print("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); + } - print("navdisplay.mfd:ND layer setup completed"); + # 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 - # start the update timer, which makes sure that the update() will be called + #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()) { - setprop(me.get_full_switch_path('toggle_display_mode'),getprop(me.get_full_switch_path('toggle_display_mode'))); - } 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() ); - }); + + # 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!"), + 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; + + 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 m3 = 1.175; var m4 = -0.0023; - var p1 = 111412.84; - var p2 = -93.5; - var p3 = 0.118; + 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')) { + 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; @@ -615,49 +674,105 @@ var NavDisplay = { var userTrk=userTrkMag; } if (me.aircraft_source.get_spd() < 80) - userTrk = userHdg; - + 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; + return; } - - if (me.get_switch('toggle_centered') or me.in_mode('toggle_display_mode', ['PLAN'])) + + 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 + # 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.windArrow.setRotation((getprop("/environment/wind-from-heading-deg")-userHdgMag)*D2R); - me.symbols.wind.setText(sprintf("%3.0f / %2.0f",getprop("/environment/wind-from-heading-deg"),getprop("/environment/wind-speed-kt"))); - - if ((var navid0=getprop("instrumentation/nav/nav-id"))!=nil ) - me.symbols.vorLId.setText(navid0); - if ((var navid1=getprop("instrumentation/nav[1]/nav-id"))!=nil ) - me.symbols.vorRId.setText(navid1); - if((var nav0dist=getprop("instrumentation/nav/nav-distance"))!=nil ) - me.symbols.dmeLDist.setText(sprintf("%3.1f",nav0dist*0.000539)); - if((var nav1dist=getprop("instrumentation/nav[1]/nav-distance"))!=nil ) - me.symbols.dmeRDist.setText(sprintf("%3.1f",nav1dist*0.000539)); - - me.symbols.range.setText(sprintf("%3.0f",me.rangeNm() )); - # reposition the map, change heading & range: + me.symbols.windArrow.setRotation((getprop("/environment/wind-from-heading-deg")-userHdg)*D2R); + 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.hide(); + 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")); @@ -668,26 +783,37 @@ var NavDisplay = { me.map._node.getNode("ref-lat",1).setDoubleValue(userLat); me.map._node.getNode("ref-lon",1).setDoubleValue(userLon); } - me.map._node.getNode("range",1).setDoubleValue(me.rangeNm()/2); # avoid this here, use a listener instead + # The set range of the map does not correspond to what we see in-sim!! + me.map._node.getNode("range",1).setDoubleValue(me.rangeNm()/3.2); # avoid this here, use a listener instead + var vhdg_bug = getprop("autopilot/settings/heading-bug-deg"); if(me.in_mode('toggle_display_mode', ['MAP'])) { - me.symbols.rotateComp.setRotation(-userTrk*D2R); - me.symbols.rotateComp2.setRotation(-userTrk*D2R); + 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*D2R); - me.symbols.curHdgPtr2.setRotation(userHdg*D2R); - me.map._node.getNode("hdg",1).setDoubleValue(userTrk); + 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"); } if(me.in_mode('toggle_display_mode', ['APP','VOR'])) { - me.symbols.rotateComp.setRotation(-userHdg*D2R); - me.symbols.rotateComp2.setRotation(-userHdg*D2R); + 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(userHdg*D2R); - me.symbols.curHdgPtr2.setRotation(userHdg*D2R); - me.map._node.getNode("hdg",1).setDoubleValue(userHdg); + 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"); @@ -698,7 +824,7 @@ var NavDisplay = { 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.show(); me.symbols.locPtr.setTranslation(deflection*150,0); if(abs(deflection < 0.99)) me.symbols.locPtr.setColorFill(1,0,1,1); @@ -719,46 +845,142 @@ var NavDisplay = { 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']); - if (!me.get_switch('toggle_centered') and me.in_mode('toggle_display_mode', ['APP','MAP','VOR'])) { - me.symbols.trkInd.show(); - me.symbols.staFromL.setVisible(staPtrVis); - me.symbols.staFromL2.hide(); - me.symbols.staFromR.setVisible(staPtrVis); - me.symbols.staFromR2.hide(); - me.symbols.staToL.setVisible(staPtrVis); - me.symbols.staToL2.hide(); - me.symbols.staToR.setVisible(staPtrVis); - me.symbols.staToR2.hide(); - me.symbols.rotateComp.setVisible(staPtrVis); - me.symbols.rotateComp2.hide(); - } else { - me.symbols.trkInd.hide(); - me.symbols.staFromL.hide(); - me.symbols.staFromL2.setVisible(staPtrVis); - me.symbols.staFromR.hide(); - me.symbols.staFromR2.setVisible(staPtrVis); - me.symbols.staToL.hide(); - me.symbols.staToL2.setVisible(staPtrVis); - me.symbols.staToR.hide(); - me.symbols.staToR2.setVisible(staPtrVis); - me.symbols.rotateComp.hide(); - me.symbols.rotateComp2.setVisible(staPtrVis); + 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); + } 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); + } } + 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")-getprop("instrumentation/altimeter/indicated-altitude-ft"); if (abs(altDiff) > 50 and altDiff/userVSpd > 0) { @@ -775,28 +997,7 @@ var NavDisplay = { } else { me.symbols.altArc.hide(); } - - ## these would require additional arguments to be moved to an external config hash currently - - me.symbols.selHdg.setRotation(getprop("autopilot/settings/true-heading-deg")*D2R); - me.symbols.selHdg2.setRotation(getprop("autopilot/settings/true-heading-deg")*D2R); - if (var nav0hdg=getprop("instrumentation/nav/heading-deg") != nil) { - me.symbols.staFromL.setRotation((nav0hdg-userHdgMag+180)*D2R); - me.symbols.staFromL2.setRotation((nav0hdg-userHdgMag+180)*D2R); - } - if (var nav0hdg=getprop("instrumentation/nav/heading-deg") != nil) { - me.symbols.staToL.setRotation((nav0hdg-userHdgMag)*D2R); - me.symbols.staToL2.setRotation((nav0hdg-userHdgMag)*D2R); - } - if (var nav1hdg=getprop("instrumentation/nav[1]/heading-deg") != nil) { - me.symbols.staFromR.setRotation((nav1hdg-userHdgMag+180)*D2R); - me.symbols.staFromR2.setRotation((nav1hdg-userHdgMag+180)*D2R); - } - if (var nav1hdg=getprop("instrumentation/nav[1]/heading-deg") != nil) { - me.symbols.staToR.setRotation((nav1hdg-userHdgMag)*D2R); - me.symbols.staToR2.setRotation((nav1hdg-userHdgMag)*D2R); - } - + ## 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 @@ -804,28 +1005,21 @@ var NavDisplay = { ## 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) ) { - # print("Update predicate true for ", feature.id); - feature.impl.is_true(me, result); # pass the result to the predicate - } - else { - # print("Update predicate false for ", feature.id); - feature.impl.is_false( me, result ); # pass the result to the predicate - } + # 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'])); - } }; diff --git a/Nasal/canvas/map/route.model b/Nasal/canvas/map/route.model index ae43efb07..27dfc3487 100644 --- a/Nasal/canvas/map/route.model +++ b/Nasal/canvas/map/route.model @@ -4,10 +4,8 @@ RouteModel.new = func make(LayerModel, RouteModel); RouteModel.init = func { me._view.reset(); - if (!getprop("/autopilot/route-manager/active")) - print("Cannot draw route, route manager inactive!") and return; - - print("TODO: route.model is still an empty stub, see route.draw instead"); + if (!getprop("/autopilot/route-manager/active")) + return; ## TODO: all the model stuff is still inside the draw file for now, this just ensures that it will be called once foreach(var t; [nil] ) diff --git a/Shaders/ubershader-gbuffer.frag b/Shaders/ubershader-gbuffer.frag index ab38c13f9..12732ae51 100644 --- a/Shaders/ubershader-gbuffer.frag +++ b/Shaders/ubershader-gbuffer.frag @@ -130,7 +130,7 @@ void main (void) ////////////////////////////////////////////////////////////////////// //begin DIRT ////////////////////////////////////////////////////////////////////// - if (dirt_enabled > 0.0){ + if (dirt_enabled >= 1){ vec3 dirtFactorIn = vec3 (dirt_r_factor, dirt_g_factor, dirt_b_factor); vec3 dirtFactor = reflmap.rgb * dirtFactorIn.rgb; //dirtFactor.r = smoothstep(0.0, 1.0, dirtFactor.r);