Canvas HUD: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
(Created page with "{{Template:Canvas Navigation}} {{stub}} Rendering a basic HUD with a Canvas. thumb|C-130J basic HUD <syntaxhighlight lang="php"> # ==...")
 
m (Use Nasal highlighter)
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{stub}}
[[File:Canvas - C-130J basic HUD.jpg|thumb|C-130J basic HUD]]
{{Template:Canvas Navigation}}
{{Template:Canvas Navigation}}
{{stub}}
 
{{cquote|Moving the HUD to use the Canvas would be a great step from my point of view, since it and 2D panels (which I am happy to write the convert for!) are the last places besides the UI which make raw OpenGL calls, and hence would benefit from moving to the Canvas (and thus, to use OSG internally)|[http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg38629.html James Turner (Mon, 05 Nov 2012)]}}
 
<references/>


Rendering a basic HUD with a Canvas.
Rendering a basic HUD with a Canvas.


[[File:Canvas - C-130J basic HUD.jpg|thumb|C-130J basic HUD]]
{{note|For correct rendering (with [[Project_Rembrandt|Rembrandt deferred rendering]]) you need to register the HUD as [[Project_Rembrandt#Registering_all_translucent_surfaces|transparent surface]].
<syntaxhighlight lang="xml">
<effect>
  <inherits-from>Effects/model-transparent</inherits-from>
  <!-- Replace 'HUD.l.canvas' with the object the canvas is placed on -->
  <object-name>HUD.l.canvas</object-name>
</effect>
</syntaxhighlight>
}}


<syntaxhighlight lang="php">
<syntaxhighlight lang="nasal">
# ==============================================================================
# ==============================================================================
# Head up display
# Head up display
Line 14: Line 27:
var vec_length = func(x, y) { return math.sqrt(pow2(x) + pow2(y)); };
var vec_length = func(x, y) { return math.sqrt(pow2(x) + pow2(y)); };
var round0 = func(x) { return math.abs(x) > 0.01 ? x : 0; };
var round0 = func(x) { return math.abs(x) > 0.01 ? x : 0; };
var clamp = func(x, min, max) { return x < min ? min : (x > max ? max : x); }


var HUD = {
var HUD = {
Line 26: Line 40:
     var m = {
     var m = {
       parents: [HUD],
       parents: [HUD],
       canvas: canvas.new(HUD.canvas_settings),
       canvas: canvas.new(HUD.canvas_settings)
      text_style: {
        'font': "LiberationFonts/LiberationMono-Regular.ttf",
        'character-size': 18,
        'character-aspect-ration': 0.9
      }
     };
     };


     m.canvas.addPlacement(placement);
     m.canvas.addPlacement(placement);
     m.canvas.setColorBackground(0.36, 1, 0.3, 0.02);
     m.canvas.setColorBackground(0.36, 1, 0.3, 0.02);
     m.root = m.canvas.createGroup();
   
     m.root =
      m.canvas.createGroup()
              .setScale(1, 1/math.cos(25 * math.pi/180))
              .setTranslation(240, 180)
              .set("font", "LiberationFonts/LiberationMono-Regular.ttf")
              .setDouble("character-size", 18)
              .setDouble("character-aspect-ration", 0.9)
              .set("stroke", "rgba(0,255,0,0.9)");
    m.text =
      m.root.createChild("group")
            .set("fill", "rgba(0,255,0,0.9)");


    m.root.setScale(1, 1/math.cos(25 * math.pi/180));
    m.root.setTranslation(240, 180);
   
     # Heading
     # Heading
     m.hdg = m.root.createChild("text");
     m.hdg =
    m.hdg._node.setValues(m.text_style);
      m.text.createChild("text")
    m.hdg.setDrawMode(3);
            .setDrawMode(3)
    m.hdg.setPadding(2);
            .setPadding(2)
    m.hdg.setColor(0.36, 1, 0.3);
            .setAlignment("center-top")
    m.hdg.setColorFill(0.36, 1, 0.3);
            .setTranslation(0, -140);
    m.hdg.setAlignment("center-top");
 
    m.hdg.setTranslation(0, -140);
   
     # Airspeed
     # Airspeed
     m.airspeed = m.root.createChild("text");
     m.airspeed =
    m.airspeed._node.setValues(m.text_style);
      m.text.createChild("text")
    m.airspeed.setColor(0.36, 1, 0.3);
            .setAlignment("right-center")
    m.airspeed.setAlignment("right-center");
            .setTranslation(-180, 0);
    m.airspeed.setTranslation(-180, 0);
      
      
     # Groundspeed
     # Groundspeed
     m.groundspeed = m.root.createChild("text");
     m.groundspeed =
    m.groundspeed._node.setValues(m.text_style);
      m.text.createChild("text")
    m.groundspeed.setColor(0.36, 1, 0.3);
            .setAlignment("left-center")
    m.groundspeed.setAlignment("left-center");
            .setTranslation(-220, 90);
    m.groundspeed.setTranslation(-220, 90);
      
      
     # Vertical speed
     # Vertical speed
     m.vertical_speed = m.root.createChild("text");
     m.vertical_speed =
    m.vertical_speed._node.setValues(m.text_style);
      m.text.createChild("text")
    m.vertical_speed.setColor(0.36, 1, 0.3);
            .setFontSize(10, 0.9)
    m.vertical_speed.setFontSize(10, 0.9);
            .setAlignment("right-center")
    m.vertical_speed.setAlignment("right-center");
            .setTranslation(205, 50);
    m.vertical_speed.setTranslation(205, 50);
      
      
     # Radar altidude
     # Radar altidude
     m.rad_alt = m.root.createChild("text");
     m.rad_alt =
    m.rad_alt._node.setValues(m.text_style);
      m.text.createChild("text")
    m.rad_alt.setColor(0.36, 1, 0.3);
            .setAlignment("right-center")
    m.rad_alt.setAlignment("right-center");
            .setTranslation(220, 70);
    m.rad_alt.setTranslation(220, 70);


     # Waterline / Pitch indicator
     # Waterline / Pitch indicator
    m.root.createChild("path")
      m.root.createChild("path")
          .moveTo(-24, 0)
            .moveTo(-24, 0)
          .horizTo(-8)
            .horizTo(-8)
          .lineTo(-4, 6)
            .lineTo(-4, 6)
          .lineTo(0, 0)
            .lineTo(0, 0)
          .lineTo(4, 6)
            .lineTo(4, 6)
          .lineTo(8, 0)
            .lineTo(8, 0)
          .horizTo(24)
            .horizTo(24)
          .setStrokeLineWidth(0.9)
            .setStrokeLineWidth(0.9);
          .setColor(0.36, 1, 0.3, 0.8);
      
      
     # Flightpath/Velocity vector
     # Flightpath/Velocity vector
     m.vec_vel =
     m.fpv = m.root.createChild("group", "FPV");
      m.root.createChild("path")
    m.fpv.createChild("path")
            .moveTo(8, 0)
        .moveTo(8, 0)
            .arcSmallCCW(8, 8, 0, -16, 0)
        .arcSmallCCW(8, 8, 0, -16, 0)
            .arcSmallCCW(8, 8, 0,  16, 0)
        .arcSmallCCW(8, 8, 0,  16, 0)
            .close()
        .moveTo(-8, 0)
            .moveTo(-8, 0)
        .horiz(-16)
            .horiz(-16)
        .moveTo(8, 0)
            .moveTo(8, 0)
        .horiz(16)
            .horiz(16)
        .setStrokeLineWidth(0.9);
            .setStrokeLineWidth(0.9)
 
            .setColor(0.36, 1, 0.3, 0.8);
    # Energy/Acceleration cues
    m.energy_cue =
      m.fpv.createChild("path")
          .setStrokeLineWidth(1);
 
    m.acc =
      m.fpv.createChild("path")
          .setStrokeLineWidth(1);
      
      
     # Horizon
     # Horizon
Line 111: Line 128:
     m.h_rot  = m.horizon_group.createTransform();
     m.h_rot  = m.horizon_group.createTransform();
      
      
    # Pitch lines
     for(var i = 5; i <= 10; i += 5)
     for(var i = 5; i <= 10; i += 5)
    {
       m.horizon_group.createChild("path")
       m.horizon_group.createChild("path")
                     .moveTo(24, -i * 18)
                     .moveTo(24, -i * 18)
Line 119: Line 138:
                     .horiz(-48)
                     .horiz(-48)
                     .vert(7)
                     .vert(7)
                     .setStrokeLineWidth(1.5)
                     .setStrokeLineWidth(1.5);
                    .setColor(0,1,0, 0.65);
    }
 
   
     # Horizon line
     # Horizon line
     m.horizon =
     m.horizon_group.createChild("path")
      m.horizon_group.createChild("path")
                  .moveTo(-500, 0)
                    .moveTo(-500, 0)
                  .horizTo(500)
                    .horizTo(500)
                  .setStrokeLineWidth(1.5);
                    .setStrokeLineWidth(1.5)
                    .setColor(0,1,0, 0.65);


     m.input = {
     m.input = {
       pitch:   "/orientation/pitch-deg",
       pitch:     "/orientation/pitch-deg",
       roll:     "/orientation/roll-deg",
       roll:       "/orientation/roll-deg",
       hdg:     "/orientation/heading-deg",
       hdg:       "/orientation/heading-deg",
       speed_n: "velocities/speed-north-fps",
       speed_n:   "velocities/speed-north-fps",
       speed_e: "velocities/speed-east-fps",
       speed_e:   "velocities/speed-east-fps",
       speed_d: "velocities/speed-down-fps",
       speed_d:   "velocities/speed-down-fps",
       alpha:   "/orientation/alpha-deg",
       alpha:     "/orientation/alpha-deg",
       beta:     "/orientation/side-slip-deg",
       beta:       "/orientation/side-slip-deg",
       ias:     "/velocities/airspeed-kt",
       ias:       "/velocities/airspeed-kt",
       gs:       "/velocities/groundspeed-kt",
       gs:         "/velocities/groundspeed-kt",
       vs:       "/velocities/vertical-speed-fps",
       vs:         "/velocities/vertical-speed-fps",
       rad_alt: "/instrumentation/radar-altimeter/radar-altitude-ft",
       rad_alt:   "/instrumentation/radar-altimeter/radar-altitude-ft",
       wow_nlg: "/gear/gear[4]/wow"
       wow_nlg:   "/gear/gear[4]/wow",
      airspeed:  "/velocities/airspeed-kt",
      target_spd: "/autopilot/settings/target-speed-kt",
      acc:        "/fdm/jsbsim/accelerations/udot-ft_sec2"
     };
     };
      
      
Line 196: Line 216:
     var dir_x  = math.atan2(round0(vel_by), math.max(vel_bx, 0.01)) * 180.0 / math.pi;
     var dir_x  = math.atan2(round0(vel_by), math.max(vel_bx, 0.01)) * 180.0 / math.pi;


     me.vec_vel.setTranslation(dir_x * 18, dir_y * 18);
     me.fpv.setTranslation(dir_x * 18, dir_y * 18);
 
    var speed_error = 0;
    if( me.input.target_spd.getValue() != nil )
      speed_error = 4 * clamp(
        me.input.target_spd.getValue() - me.input.airspeed.getValue(),
        -15, 15
      );
   
    me.energy_cue.reset();
#    if( math.abs(speed_error) > 3 )
      me.energy_cue.moveTo(-22, 0)
                  .vert(speed_error)
                  .horiz(3)
                  .vertTo(0);
   
    var acc = me.input.acc.getValue() or 0;
    me.acc.reset()
          .moveTo(-34, -acc * 5 - 4)
          .line(8, 4)
          .line(-8, 4);


     settimer(func me.update(), 0);
     settimer(func me.update(), 0);

Revision as of 21:05, 10 November 2013

This article is a stub. You can help the wiki by expanding it.
C-130J basic HUD


Cquote1.png Moving the HUD to use the Canvas would be a great step from my point of view, since it and 2D panels (which I am happy to write the convert for!) are the last places besides the UI which make raw OpenGL calls, and hence would benefit from moving to the Canvas (and thus, to use OSG internally)
Cquote2.png


Rendering a basic HUD with a Canvas.

Note  For correct rendering (with Rembrandt deferred rendering) you need to register the HUD as transparent surface.
<effect>
  <inherits-from>Effects/model-transparent</inherits-from>
  <!-- Replace 'HUD.l.canvas' with the object the canvas is placed on -->
  <object-name>HUD.l.canvas</object-name>
</effect>
# ==============================================================================
# Head up display
# ==============================================================================

var pow2 = func(x) { return x * x; };
var vec_length = func(x, y) { return math.sqrt(pow2(x) + pow2(y)); };
var round0 = func(x) { return math.abs(x) > 0.01 ? x : 0; };
var clamp = func(x, min, max) { return x < min ? min : (x > max ? max : x); }

var HUD = {
  canvas_settings: {
    "name": "HUD",
    "size": [1024, 1024],
    "view": [480, 360],
    "mipmapping": 1
  },
  new: func(placement)
  {
    var m = {
      parents: [HUD],
      canvas: canvas.new(HUD.canvas_settings)
    };

    m.canvas.addPlacement(placement);
    m.canvas.setColorBackground(0.36, 1, 0.3, 0.02);
    
    m.root =
      m.canvas.createGroup()
              .setScale(1, 1/math.cos(25 * math.pi/180))
              .setTranslation(240, 180)
              .set("font", "LiberationFonts/LiberationMono-Regular.ttf")
              .setDouble("character-size", 18)
              .setDouble("character-aspect-ration", 0.9)
              .set("stroke", "rgba(0,255,0,0.9)");
    m.text =
      m.root.createChild("group")
            .set("fill", "rgba(0,255,0,0.9)");

    # Heading
    m.hdg =
      m.text.createChild("text")
            .setDrawMode(3)
            .setPadding(2)
            .setAlignment("center-top")
            .setTranslation(0, -140);

    # Airspeed
    m.airspeed =
      m.text.createChild("text")
            .setAlignment("right-center")
            .setTranslation(-180, 0);
    
    # Groundspeed
    m.groundspeed =
      m.text.createChild("text")
            .setAlignment("left-center")
            .setTranslation(-220, 90);
    
    # Vertical speed
    m.vertical_speed =
      m.text.createChild("text")
            .setFontSize(10, 0.9)
            .setAlignment("right-center")
            .setTranslation(205, 50);
    
    # Radar altidude
    m.rad_alt =
      m.text.createChild("text")
            .setAlignment("right-center")
            .setTranslation(220, 70);

    # Waterline / Pitch indicator
      m.root.createChild("path")
            .moveTo(-24, 0)
            .horizTo(-8)
            .lineTo(-4, 6)
            .lineTo(0, 0)
            .lineTo(4, 6)
            .lineTo(8, 0)
            .horizTo(24)
            .setStrokeLineWidth(0.9);
    
    # Flightpath/Velocity vector
    m.fpv = m.root.createChild("group", "FPV");
    m.fpv.createChild("path")
         .moveTo(8, 0)
         .arcSmallCCW(8, 8, 0, -16, 0)
         .arcSmallCCW(8, 8, 0,  16, 0)
         .moveTo(-8, 0)
         .horiz(-16)
         .moveTo(8, 0)
         .horiz(16)
         .setStrokeLineWidth(0.9);

    # Energy/Acceleration cues
    m.energy_cue =
      m.fpv.createChild("path")
           .setStrokeLineWidth(1);

    m.acc =
      m.fpv.createChild("path")
           .setStrokeLineWidth(1);
    
    # Horizon
    m.horizon_group = m.root.createChild("group");
    m.h_trans = m.horizon_group.createTransform();
    m.h_rot   = m.horizon_group.createTransform();
    
    # Pitch lines
    for(var i = 5; i <= 10; i += 5)
    {
      m.horizon_group.createChild("path")
                     .moveTo(24, -i * 18)
                     .horiz(48)
                     .vert(7)
                     .moveTo(-24, -i * 18)
                     .horiz(-48)
                     .vert(7)
                     .setStrokeLineWidth(1.5);
    }
    
    # Horizon line
    m.horizon_group.createChild("path")
                   .moveTo(-500, 0)
                   .horizTo(500)
                   .setStrokeLineWidth(1.5);

    m.input = {
      pitch:      "/orientation/pitch-deg",
      roll:       "/orientation/roll-deg",
      hdg:        "/orientation/heading-deg",
      speed_n:    "velocities/speed-north-fps",
      speed_e:    "velocities/speed-east-fps",
      speed_d:    "velocities/speed-down-fps",
      alpha:      "/orientation/alpha-deg",
      beta:       "/orientation/side-slip-deg",
      ias:        "/velocities/airspeed-kt",
      gs:         "/velocities/groundspeed-kt",
      vs:         "/velocities/vertical-speed-fps",
      rad_alt:    "/instrumentation/radar-altimeter/radar-altitude-ft",
      wow_nlg:    "/gear/gear[4]/wow",
      airspeed:   "/velocities/airspeed-kt",
      target_spd: "/autopilot/settings/target-speed-kt",
      acc:        "/fdm/jsbsim/accelerations/udot-ft_sec2"
    };
    
    foreach(var name; keys(m.input))
      m.input[name] = props.globals.getNode(m.input[name], 1);
    
    return m;
  },
  update: func()
  {
    me.airspeed.setText(sprintf("%d", me.input.ias.getValue()));
    me.groundspeed.setText(sprintf("G %3d", me.input.gs.getValue()));
    me.vertical_speed.setText(sprintf("%.1f", me.input.vs.getValue() * 60.0 / 1000));
    
    var rad_alt = me.input.rad_alt.getValue();
    if( rad_alt and rad_alt < 5000 ) # Only show below 5000AGL
      rad_alt = sprintf("R %4d", rad_alt);
    else
      rad_alt = nil;
    me.rad_alt.setText(rad_alt);
    
    me.hdg.setText(sprintf("%03d", me.input.hdg.getValue()));
    me.h_trans.setTranslation(0, 18 * me.input.pitch.getValue());
    
    var rot = -me.input.roll.getValue() * math.pi / 180.0;
    me.h_rot.setRotation(rot);
    
    # flight path vector (FPV)
    var vel_gx = me.input.speed_n.getValue();
    var vel_gy = me.input.speed_e.getValue();
    var vel_gz = me.input.speed_d.getValue();
    
    var yaw = me.input.hdg.getValue() * math.pi / 180.0;
    var roll = me.input.roll.getValue() * math.pi / 180.0;
    var pitch = me.input.pitch.getValue() * math.pi / 180.0;
    
    var sy = math.sin(yaw);   var cy = math.cos(yaw);
    var sr = math.sin(roll);  var cr = math.cos(roll);
    var sp = math.sin(pitch); var cp = math.cos(pitch);

    var vel_bx = vel_gx * cy * cp
               + vel_gy * sy * cp
               + vel_gz * -sp;
    var vel_by = vel_gx * (cy * sp * sr - sy * cr)
               + vel_gy * (sy * sp * sr + cy * cr)
               + vel_gz * cp * sr;
    var vel_bz = vel_gx * (cy * sp * cr + sy * sr)
               + vel_gy * (sy * sp * cr - cy * sr)
               + vel_gz * cp * cr;

    var dir_y = math.atan2(round0(vel_bz), math.max(vel_bx, 0.01)) * 180.0 / math.pi;
    var dir_x  = math.atan2(round0(vel_by), math.max(vel_bx, 0.01)) * 180.0 / math.pi;

    me.fpv.setTranslation(dir_x * 18, dir_y * 18);

    var speed_error = 0;
    if( me.input.target_spd.getValue() != nil )
      speed_error = 4 * clamp(
        me.input.target_spd.getValue() - me.input.airspeed.getValue(),
        -15, 15
      );
    
    me.energy_cue.reset();
#    if( math.abs(speed_error) > 3 )
      me.energy_cue.moveTo(-22, 0)
                   .vert(speed_error)
                   .horiz(3)
                   .vertTo(0);
    
    var acc = me.input.acc.getValue() or 0;
    me.acc.reset()
          .moveTo(-34, -acc * 5 - 4)
          .line(8, 4)
          .line(-8, 4);

    settimer(func me.update(), 0);
  }
};

var init = setlistener("/sim/signals/fdm-initialized", func() {
  removelistener(init); # only call once
  var hud_pilot = HUD.new({"node": "HUD.l.canvas"});
  hud_pilot.update();
#  var hud_copilot = HUD.new({"node": "HUD.l.canvas.001"});
#  hud_copilot.update();
});