1
0
Fork 0

Canvas ND: use MapStructure for TFC, other misc.

Implement traffic in MapStructure and use it. Various other hacks and/or
cleanup. Feedback required on whether this is a lot better than before.

Also partially revert 9c018d94c4d88dad7476ec250fa3b52024526f4b to add
feature to geo.PositionedSearch: it me._equals is overridden then the
old mechanism is used instead of the new C++ function, so that the
custom equality can be used. (In particular for the Fixes with the
TrafficModel class).
This commit is contained in:
Philosopher 2014-01-26 20:18:30 -06:00
parent 0ee6a5c71e
commit 0d4a86e3d4
8 changed files with 295 additions and 32 deletions

View file

@ -170,7 +170,7 @@ Symbol.Controller = {
# Return whether this symbol/object is visible:
isVisible: func(model) return 1,
# Get the position of this symbol/object:
getpos: func(model), # default provided below
getpos: func(model) , # default provided below
}; # of Symbol.Controller
var getpos_fromghost = func(positioned_g)
@ -187,6 +187,11 @@ Symbol.Controller.getpos = func(obj) {
if (typeof(obj) == 'hash')
if (isa(obj, geo.Coord))
return obj.latlon();
if (isa(obj, props.Node))
return [
obj.getValue("position/latitude-deg") or obj.getValue("latitude-deg"),
obj.getValue("position/longitude-deg") or obj.getValue("longitude-deg")
];
if (contains(obj,'lat') and contains(obj,'lon'))
return [obj.lat, obj.lon];
@ -194,6 +199,22 @@ Symbol.Controller.getpos = func(obj) {
die("no suitable getpos() found! Of type: "~typeof(obj));
};
Symbol.Controller.equals = func(l, r) {
if (l == r) return 1;
var t = typeof(l);
if (t == 'ghost')
return 0;#l.id == r.id;
if (t == 'hash')
if (isa(l, props.Node))
return l.equals(r);
else {
foreach (var k; keys(l))
if (l[k] != r[k]) return 0;
return 1;
}
die("bad types");
};
var assert_m = func(hash, member)
if (!contains(hash, member))
@ -306,6 +327,7 @@ var SymbolLayer = {
group: group.createChild("group", me.id), # TODO: the id is not properly set, but would be useful for debugging purposes (VOR, FIXES, NDB etc)
list: [],
};
m.searcher = geo.PositionedSearch.new(me.searchCmd, me.onAdded, me.onRemoved, m);
# FIXME: hack to expose type of layer:
if (caller(1)[1] == Map.addLayer) {
var this_type = caller(1)[0].type_arg;
@ -323,7 +345,6 @@ var SymbolLayer = {
if (controller.parents[0].parents[0] != SymbolLayer.Controller)
die("OOP error");
m.controller = controller;
m.searcher = geo.PositionedSearch.new(me.searchCmd, me.onAdded, me.onRemoved, m);
m.update();
return m;
},
@ -338,12 +359,13 @@ var SymbolLayer = {
foreach (var e; me.list)
e.del();
},
findsym: func(positioned_g, del=0) {
findsym: func(model, del=0) {
forindex (var i; me.list) {
var e = me.list[i];
if (geo.PositionedSearch._equals(e.model, positioned_g)) {
if (Symbol.Controller.equals(e.model, model)) {
if (del) {
# Remove this element from the list
# TODO: maybe C function for this? extend pop() to accept index?
var prev = subvec(me.list, 0, i);
var next = subvec(me.list, i+1);
me.list = prev~next;
@ -355,11 +377,11 @@ var SymbolLayer = {
},
searchCmd: func() me.controller.searchCmd(),
# Adds a symbol.
onAdded: func(positioned_g)
append(me.list, Symbol.new(me.type, me.group, positioned_g)),
# Removes a symbol
onRemoved: func(positioned_g)
me.findsym(positioned_g, 1).del(),
onAdded: func(model)
append(me.list, Symbol.new(me.type, me.group, model)),
# Removes a symbol.
onRemoved: func(model)
me.findsym(model, 1).del(),
}; # of SymbolLayer
# Class to manage controlling a #SymbolLayer.
@ -397,7 +419,7 @@ var CompassLayer = {
var AltitudeArcLayer = {
};
load_MapStructure = func {
var load_MapStructure = func {
Map.Controller = {
# Static/singleton:
registry: {},
@ -453,7 +475,7 @@ load_MapStructure = func {
load(FG_ROOT~"/Nasal/canvas/map/"~name~".scontroller", name);
}
foreach( var name; ['VOR','FIX','NDB','DME','WPT'] )
foreach( var name; ['VOR','FIX','NDB','DME','WPT','TFC'] )
load_deps( name );
load(FG_ROOT~"/Nasal/canvas/map/aircraftpos.controller", name);

View file

@ -0,0 +1,98 @@
# Class things:
var name = 'TFC';
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 model_root = props.globals.getNode("/ai/models/");
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
listeners: [],
query_range_nm: 25,
};
# Listen to ai model events
append(m.listeners, setlistener(
model_root.getNode("model-added"), func(n) {
var node = props.globals.getNode(n.getValue());
var name = node.getName();
if (name == "aircraft" or name == "multiplayer")
layer.onAdded(TrafficModel.new(node));
}
));
append(m.listeners, setlistener(
model_root.getNode("model-removed"), func(n) {
var node = props.globals.getNode(n.getValue());
var name = node.getName();
if (name == "aircraft" or name == "multiplayer")
layer.onRemoved(TrafficModel.new(node));
}
));
layer.searcher._equals = func(l,r) l.equals(r);
return m;
};
var del = func() {
#print(name~".lcontroller.del()");
foreach (var l; me.listeners)
removelistener(l);
};
var TrafficModel = {
new: func(node, id=nil, layer=nil) {
if (id == nil) id = node.getValue("id");
var m = {
# Note: because this inherits from props.Node, Symbol.Controller.equals
# will call l.equals(r) -- the one defined below
parents: [TrafficModel, geo.Coord, node],
id: id,
node: node,
pos: node.getNode("position"),
};
if (m.pos == nil)
m.latlon = func [nil,nil,nil];
return m;
},
equals: func(other) other.id == me.id,
latlon: func() {
return [
me.pos.getValue("latitude-deg"),
me.pos.getValue("longitude-deg"),
me.pos.getValue("altitude-ft")
];
},
};
var searchCmd = func {
# TODO: this would be a good candidate for splitting across frames
#print("Doing query: "~name);
var result = [];
var pos = geo.Coord.new(); # FIXME: all of these should be instance variables
var myPosition = geo.Coord.new();
# FIXME: need a Map Controller for this, and all query_range's/get_position's
var myPositionVec = me.get_position();
myPosition.set_latlon( myPositionVec[0], myPositionVec[1]);
var max_dist_m = me.query_range()*NM2M;
# AI and Multiplayer traffic
foreach (var traffic; [model_root.getChildren("aircraft"), model_root.getChildren("multiplayer")])
foreach(var t; traffic) {
pos.set_latlon(t.getValue("position/latitude-deg"),
t.getValue("position/longitude-deg"));
if (pos.distance_to( myPosition ) <= max_dist_m )
append(result, TrafficModel.new(t, nil, me.layer));
}
#debug.dump(result);
#return [];
return result;
};

View file

@ -0,0 +1,12 @@
# Class things:
var name = 'TFC';
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
# XXX: this is more model-ish than controller-ish
var get_threat_lvl = func(model) model.getValue("tcas/threat-level");
var get_vspd = func(model) (model.getValue("velocities/vertical-speed-fps") or 0)*60;
var get_alt_diff = func(model) (model.getValue("position/altitude-ft") or 0) - (getprop("/position/altitude-ft") or 0);

View file

@ -0,0 +1,91 @@
# Class things:
var name = 'TFC';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element"
var text_tcas = nil;
var icon_tcas = nil;
var arrow_tcas = [nil,nil];
var arrow_type = nil;
var draw_tcas_arrow = nil;
var draw = func {
if (me.draw_tcas_arrow == nil)
me.draw_tcas_arrow = [
draw_tcas_arrow_above_500,
draw_tcas_arrow_below_500
];
#var callsign = me.model.getNode("callsign").getValue();
# print("Drawing traffic for:", callsign );
var threatLvl = me.controller.get_threat_lvl(me.model);
var vspeed = me.controller.get_vspd(me.model);
var altDiff = me.controller.get_alt_diff(me.model);
# Init
if (me.text_tcas == nil) {
me.text_tcas = me.element.createChild("text")
.setDrawMode( canvas.Text.TEXT )
.setText(sprintf("%+02.0f",altDiff/100))
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setColor(1,1,1)
.setFontSize(28)
.setAlignment("center-center");
me.icon_tcas = me.element.createChild("path")
.setStrokeLineWidth(3);
}
# Update
if (altDiff > 0)
me.text_tcas.setTranslation(0,-40);
else
me.text_tcas.setTranslation(0,40);
var arrow_type = (vspeed >= 500);
if (arrow_type != me.arrow_type) {
(old_type, me.arrow_type) = (me.arrow_type, arrow_type);
if (old_type != nil and me.arrow_tcas[old_type] != nil) me.arrow_tcas[old_type].hide();
if (me.arrow_tcas[arrow_type] == nil)
me.arrow_tcas[arrow_type] = me.draw_tcas_arrow[arrow_type](me.element);
me.arrow_tcas[arrow_type].show();
}
## TODO: threat level symbols should also be moved to *.draw files
if (threatLvl == 3) {
# resolution advisory
me.icon_tcas.moveTo(-17,-17)
.horiz(34)
.vert(34)
.horiz(-34)
.close()
.setColor(1,0,0)
.setColorFill(1,0,0);
me.text_tcas.setColor(1,0,0);
me.arrow_tcas.setColor(1,0,0);
} elsif (threatLvl == 2) {
# traffic advisory
me.icon_tcas.moveTo(-17,0)
.arcSmallCW(17,17,0,34,0)
.arcSmallCW(17,17,0,-34,0)
.setColor(1,0.5,0)
.setColorFill(1,0.5,0);
me.text_tcas.setColor(1,0.5,0);
me.arrow_tcas.setColor(1,0.5,0);
} elsif (threatLvl == 1) {
# proximate traffic
me.icon_tcas.moveTo(-10,0)
.lineTo(0,-17)
.lineTo(10,0)
.lineTo(0,17)
.close()
.setColor(1,1,1)
.setColorFill(1,1,1);
} else {
# other traffic
me.icon_tcas.moveTo(-10,0)
.lineTo(0,-17)
.lineTo(10,0)
.lineTo(0,17)
.close()
.setColor(1,1,1);
}
};

View file

@ -1,11 +1,12 @@
# Class things:
var name = 'VOR';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add("VOR", __self__);
Symbol.registry["VOR"].df_controller = __self__;
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["VOR"];
var LayerController = SymbolLayer.Controller.registry[name];
var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func()
die("VOR.scontroller.query_range /MUST/ be provided by implementation");
die(name~".scontroller.query_range /MUST/ be provided by implementation");

View file

@ -14,22 +14,21 @@ var new = func(layer) {
layer: layer,
listeners: [],
query_range_nm: 25,
query_type:'vor',
};
return m;
};
var del = func() {
#print("VOR.lcontroller.del()");
#print(name~"VOR.lcontroller.del()");
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
#print("Running query: WPT");
#print("Running query: "~name);
var fp = flightplan();
var fpSize = fp.getPlanSize();
var result = [];
var result = [];
for (var i = 1; i <fpSize; i+=1)
append(result, fp.getWP(i).path()[0] );

View file

@ -132,12 +132,11 @@ var NDStyles = {
{ name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
# FIXME: this is a really ugly place for controller code
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
# 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") {
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: DME");
# (Hopefully) smart update
layer.update();
@ -145,7 +144,7 @@ var NDStyles = {
}, # end of layer update predicate
}, # end of DME layer
{ name:'mp-traffic', update_on:['toggle_range','toggle_traffic'],
{ name:'mp-traffic', disabled:1, update_on:['toggle_range','toggle_traffic'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_traffic');
layer._view.setVisible( visible );
@ -154,6 +153,16 @@ var NDStyles = {
}
}, # end of layer update predicate
}, # end of traffic layer
{ name:'TFC', isMapStructure:1, update_on:['toggle_range','toggle_traffic'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_traffic');
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: TFC");
layer.update();
}
}, # end of layer update predicate
}, # end of traffic layer
{ name:'runway-nd', update_on:['toggle_range','toggle_display_mode'],
predicate: func(nd, layer) {
@ -560,6 +569,7 @@ var NavDisplay = {
}
var get_current_position = func {
delete(caller(0)[0], "me"); # remove local me, inherit outer one
return [
me.aircraft_source.get_lat(), me.aircraft_source.get_lon()
];
@ -581,6 +591,8 @@ var NavDisplay = {
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;
canvas.SymbolLayer.Controller.get("TFC").query_range = controller.query_range;
canvas.SymbolLayer.Controller.get("TFC").get_position = controller.get_position;
###
# set up various layers, controlled via callbacks in the controller hash

View file

@ -341,17 +341,45 @@ var PositionedSearch = {
};
},
_equals: func(a,b) {
if (a == nil or b == nil) return 0;
return (a == b or a.id == b.id);
return a == b; # positioned objects are created once
#return (a == b or a.id == b.id);
},
condense: func(vec) {
var ret = [];
foreach (var e; vec)
if (e != nil) append(ret, e);
return ret;
},
diff: func(old, new) {
var removed = old~[]; #copyvec
var added = new~[];
# Mark common elements from removed and added:
forindex (OUTER; var i; removed)
forindex (var j; new)
if (removed[i] != nil and added[j] != nil and me._equals(removed[i], added[j])) {
removed[i] = added[j] = nil;
continue OUTER;
}
# And remove those common elements, returning the result:
return [new, me.condense(removed), me.condense(added)];
},
update: func(searchCmd=nil) {
if (searchCmd == nil) searchCmd = me.searchCmd;
var old = me.result~[]; #copyvec
me.result = call(searchCmd, nil, me.obj);
positioned.diff( old,
me.result,
func call(me.onAdded, arg, me.obj),
func call(me.onRemoved, arg, me.obj) );
if (me._equals == PositionedSearch._equals) {
# Optimized search using C code
var old = me.result~[]; #copyvec
me.result = call(searchCmd, nil, me.obj);
positioned.diff( old,
me.result,
func call(me.onAdded, arg, me.obj),
func call(me.onRemoved, arg, me.obj) );
} else {
(me.result, var removed, var added) = me.diff(me.result, call(searchCmd, nil, me.obj));
foreach (var e; removed)
call(me.onRemoved, [e], me.obj);
foreach (var e; added)
call(me.onAdded, [e], me.obj);
}
},
# this is the worst case scenario: switching from 640 to 320 (or vice versa)
test: func(from=640, to=320) {