1 2 3var nasal_dir = getprop("/sim/fg-root") ~ "/Aircraft/Instruments-3d/ISFD/"; 4io.load_nasal(nasal_dir ~ 'ISFD_gui.nas', "isfd"); 5io.load_nasal(nasal_dir ~ 'ISFDGenericController.nas', "isfd"); 6io.load_nasal(nasal_dir ~ 'PhysicalController.nas', "isfd"); 7 8# main wrapper object 9var ISFD = { 10 11baseSize: 512, 12halfSize: 256, 13hsiHeight: 358, 14hsiWidth: 306, 15speedTapeWidth: 92, 16altTapeWidth: 114, 17pitchLadderDegreeSpacing: 8, 18rollBaseRadius: 160, 19roseRadius: 512, 20boxHeight: 48, 21 22needleBoxSize: 220, 23needleBoxHeight: 20, 24 25new : func(controller = nil) { 26 var obj = { 27 parents : [ISFD], 28 _mode : '' 29 }; 30 31 ISFD.hsiLeft = ISFD.speedTapeWidth - ISFD.halfSize; 32 ISFD.hsiXCenter = ISFD.hsiLeft + ISFD.hsiWidth/2; 33 ISFD.hsiRight = ISFD.halfSize - ISFD.altTapeWidth; 34 ISFD.hsiBottom = ISFD.hsiHeight/2; 35 ISFD.modeBoxHeight = (ISFD.baseSize - ISFD.hsiHeight)/2; 36 37 obj.canvas = canvas.new({ 38 "name" : "ISFD Canvas", 39 "size" : [ISFD.baseSize, ISFD.baseSize], 40 "view" : [ISFD.baseSize, ISFD.baseSize], 41 "mipmapping": 0, 42 }); 43 44 obj.root = obj.canvas.createGroup(); 45 # centering transform 46 obj.root.setTranslation(ISFD.halfSize, ISFD.halfSize); 47 48 var controllerClass = (controller == nil) ? isfd.GenericController : controller; 49 obj._controller = controllerClass.new(obj); 50 obj.createContents(); 51 obj._updateTimer = maketimer(0.05, func obj.update(); ); 52 obj._updateTimer.start(); 53 obj._appMode = nil; # set to nil to force update on first update() trigger 54 55 return obj; 56}, 57 58del: func() 59{ 60 print('Deleting ISFD'); 61 me._updateTimer.stop(); 62}, 63 64# display the ISFD canvas on the specified object 65display : func(target_object) { 66 me.canvas.addPlacement({"node": target_object}); 67}, 68 69displayGUI : func(my_isfd, scale=1.0) { 70 var gui = isfd.GUI.new(my_isfd, my_isfd.canvas, scale); 71}, 72 73createContents : func() 74{ 75 me.createPitchLadder(); 76 me.createRollTicks(); 77 me.createCompassRose(); 78 me.createAltitudeTape(); 79 me.createSpeedTape(); 80 81 me.createSpeedBox(); 82 me.createAltitudeBox(); 83 84 me.createAltimeterSetting(); 85 # mach readout - not on the B737 model? 86 me.createModeText(); 87 88 me.createLocalizer(); 89 me.createGlideslope(); 90 91 me.createAirplaneMarker(); 92 93}, 94 95createAirplaneMarker : func() 96{ 97 var markerGroup = me.root.createChild("group", "airplane-indicator-group"); 98 markerGroup.setTranslation(ISFD.hsiXCenter, 0); 99 100 var m = markerGroup.createChild("path", "airplane-indicator"); 101 m.setColorFill(0, 0, 0); 102 m.setStrokeLineWidth(2); 103 m.setColor(1, 1, 1); 104 105 var markerWidth = 8; 106 var hw = markerWidth / 2; 107 var horWidth = 80; 108 var vertExtension = markerWidth * 1.5; 109 110 m.moveTo(-hw, -hw); 111 m.line(markerWidth, 0); 112 m.line(0, markerWidth); 113 m.line(-markerWidth, 0); 114 m.close(); 115 116 # left L 117 m.moveTo(-hw - markerWidth, -hw); 118 m.line(-horWidth, 0); 119 m.line(0, markerWidth); 120 m.line(horWidth - markerWidth, 0); 121 m.line(0, vertExtension); 122 m.line(markerWidth, 0); 123 m.close(); 124 125 126 # right L 127 m.moveTo(hw + markerWidth, -hw); 128 m.line(horWidth, 0); 129 m.line(0, markerWidth); 130 m.line(markerWidth - horWidth, 0); 131 m.line(0, vertExtension); 132 m.line(-markerWidth, 0); 133 m.close(); 134}, 135 136# add a single radially aligned tick mark between radiui one and two 137addPolarTick : func(path, angle, r1, r2) 138{ 139 var horAngle = angle + 90; # angle from +ve X axis 140 var sa = math.sin(horAngle * D2R); 141 var ca = math.cos(horAngle * D2R); 142 143 path.moveTo(ca * r1, sa * r1); 144 path.lineTo(ca * r2, sa * r2); 145 return path; 146}, 147 148addHorizontalSymmetricPolarTick : func(path, angle, r1, r2) 149{ 150 var horAngle = angle + 90; # angle from +ve X axis 151 var sa = -math.sin(horAngle * D2R); 152 var ca = math.cos(horAngle * D2R); 153 154 path.moveTo(ca * r1, sa * r1); 155 path.lineTo(ca * r2, sa * r2); 156 path.moveTo(-ca * r1, sa * r1); 157 path.lineTo(-ca * r2, sa * r2); 158 return path; 159}, 160 161addHorizontalSymmetricLine : func(path, positiveLength, y) 162{ 163 path.moveTo(-positiveLength, y); 164 path.lineTo(positiveLength, y); 165 return path; 166}, 167 168addDiamond : func(path, radius) 169{ 170 path.move(-radius, 0); 171 path.line(radius, -radius); # top 172 path.line(radius, radius); # right 173 path.line(-radius, radius); # bottom 174 path.close(); 175}, 176 177createDigitTape : func(parent, name, suffix = nil) 178{ 179 var t = parent.createChild('text', name); 180 # 'top' zero (above 9) 181 var s = '0' ~ chr(10); 182 if (suffix != nil) { 183 s = '0' ~ suffix ~ chr(10); 184 } 185 186 for (var i=9; i>=0; i-=1) { 187 if (suffix != nil) { 188 s = s ~ i ~ suffix ~ chr(10); 189 } else { 190 s = s ~ i ~ chr(10); 191 } 192 } 193 t.setText(s); 194 t.setFont("LiberationFonts/LiberationMono-Regular.ttf"); 195 t.setFontSize(44); 196 # t.set('line-height', 0.9); 197 t.setAlignment("left-bottom"); 198 return t; 199}, 200 201createRollTicks : func() 202{ 203 # these don't move! 204 # center filled white arrow pointing down 205 # large tick at 30 deg 206 # minor tick at 10, 20 deg 207 # minor tick at 45? 208 # and major tick at 60 by the looks of it 209 210 me._rollGroup = me.root.createChild("group", "roll-group"); 211 me._rollGroup.setTranslation(ISFD.hsiXCenter, 0); 212 me._rollGroup.set("clip-frame", canvas.Element.GLOBAL); 213 me._rollGroup.set("clip", "rect(77px, 398px, 435px, 92px)"); 214 215 var rollScale = me.root.createChild("path", "roll-scale"); 216 rollScale.setTranslation(ISFD.hsiXCenter, 0); 217 218 rollScale.setStrokeLineWidth(2); 219 rollScale.setColor(1, 1, 1); 220 var baseR = ISFD.rollBaseRadius; 221 var minorTick = 16; 222 var majorTick = 30; 223 224 me.addHorizontalSymmetricPolarTick(rollScale, 10, baseR, baseR + minorTick); 225 me.addHorizontalSymmetricPolarTick(rollScale, 20, baseR, baseR + minorTick); 226 me.addHorizontalSymmetricPolarTick(rollScale, 30, baseR, baseR + majorTick); 227 me.addHorizontalSymmetricPolarTick(rollScale, 45, baseR, baseR + minorTick); 228 229 # we cap the length of these to avoid sticking into the speed/alt tapes 230 me.addHorizontalSymmetricPolarTick(rollScale, 60, baseR, baseR + minorTick); 231 rollScale.close(); 232 233 # add filled path for the zero arrow 234 rollZeroArrow = me.root.createChild("path", "roll-zero-mark"); 235 rollZeroArrow.setColorFill(1, 1, 1); 236 rollZeroArrow.setTranslation(ISFD.hsiXCenter, 0); 237 238 # arrow extends from the roll radius to the top of HSI 239 var arrowHeight = (ISFD.hsiHeight / 2) - ISFD.rollBaseRadius; 240 var arrowHWidth = arrowHeight * (2/3); 241 242 rollZeroArrow.moveTo(0, -baseR); 243 rollZeroArrow.line(arrowHWidth, -arrowHeight); 244 rollZeroArrow.line(-arrowHWidth * 2, 0); 245 rollZeroArrow.close(); 246 247 # and the moving arrow 248 var rollMarker = me._rollGroup.createChild("path", "roll-indicator"); 249 rollMarker.setColorFill(0, 0, 0); 250 rollMarker.setStrokeLineWidth(2); 251 rollMarker.setColor(1, 1, 1); 252 rollMarker.moveTo(0, -baseR); 253 rollMarker.line(arrowHWidth, arrowHeight); 254 rollMarker.line(-arrowHWidth * 2, 0); 255 rollMarker.close(); 256}, 257 258createPitchLadder : func() 259{ 260 # sky rect 261 var box = me.root.rect(ISFD.hsiLeft, -ISFD.hsiHeight/2, 262 ISFD.hsiWidth, ISFD.hsiHeight); 263 box.setColorFill('#69B3F4'); 264 265 me._pitchRotation = me.root.createChild("group", "pitch-rotation"); 266 me.pitchGroup = me._pitchRotation.createChild("group", "pitch-group"); 267 268 var bkGroup = me.root.createChild("group", "hsi-clip-group"); 269 270 271 # ground rect 272 var box = me.pitchGroup.rect(-1000, 0, 2000, 2000); 273 box.setColorFill('#8F9552'); 274 box.set("clip-frame", canvas.Element.GLOBAL); 275 box.set("clip", "rect(77px, 398px, 435px, 92px)"); 276 277 var ladderGroup = me.pitchGroup.createChild("group", "pitch-ladder"); 278 ladderGroup.set("clip-frame", canvas.Element.GLOBAL); 279 ladderGroup.set("clip", "rect(140px, 368px, 435px, 122px)"); 280 281 var pitchLadder = ladderGroup.createChild("path", "pitch-ladder-ticks"); 282 pitchLadder.setStrokeLineWidth(2); 283 pitchLadder.setColor(1, 1, 1); 284 285 var sp = ISFD.pitchLadderDegreeSpacing; # shorthand 286 var tenDegreeWidth = 64; 287 var fiveDegreeWidth = 32; 288 var twoFiveDegreeWidth = 24; 289 290 # add line at zero 291 me.addHorizontalSymmetricLine(pitchLadder, ISFD.halfSize, 0); 292 293 for (var i=1; i<=9; i+=1) 294 { 295 var d = i * 10; 296 me.addHorizontalSymmetricLine(pitchLadder, tenDegreeWidth, d * sp); 297 me.addHorizontalSymmetricLine(pitchLadder, tenDegreeWidth, -d * sp); 298 299 me.addHorizontalSymmetricLine(pitchLadder, fiveDegreeWidth, (d - 5) * sp); 300 me.addHorizontalSymmetricLine(pitchLadder, fiveDegreeWidth, (5 - d) * sp); 301 302 # 2.5 and 7.5 degree lines 303 me.addHorizontalSymmetricLine(pitchLadder, twoFiveDegreeWidth, (d - 2.5) * sp); 304 me.addHorizontalSymmetricLine(pitchLadder, twoFiveDegreeWidth, (2.5 - d) * sp); 305 me.addHorizontalSymmetricLine(pitchLadder, twoFiveDegreeWidth, (d - 7.5) * sp); 306 me.addHorizontalSymmetricLine(pitchLadder, twoFiveDegreeWidth, (7.5 - d) * sp); 307 308 # add text as well 309 var textUp = ladderGroup.createChild("text", "pitch-ladder-legend-" ~ d); 310 textUp.setText(d); 311 textUp.setAlignment("right-center"); 312 textUp.setTranslation(-tenDegreeWidth, d * sp); 313 textUp.setFontSize(36); 314 textUp.setFont("LiberationFonts/LiberationMono-Bold.ttf"); 315 316 var textDown = ladderGroup.createChild("text", "pitch-ladder-legend-" ~ d); 317 textDown.setText(d); 318 textDown.setAlignment("right-center"); 319 textDown.setTranslation(-tenDegreeWidth, -d * sp); 320 textDown.setFontSize(36); 321 textDown.setFont("LiberationFonts/LiberationMono-Bold.ttf"); 322 } 323}, 324 325createSpeedTape : func() 326{ 327 # background box 328 var box = me.root.rect(0, -ISFD.halfSize, ISFD.speedTapeWidth - 1, ISFD.baseSize); 329 box.setColorFill('#738A7E'); 330 box.setTranslation(-256, 0); 331 box.set('z-index', -1); 332 333 # short lines for text 334 # long lines for 'odd' tens 335 # 3 digit monospace text fits exactly between left side and short tick 336 # maximum 5 (6?) visible text pieces 337 338 me._speedTapeGroup = me.root.createChild("group", "speed-tape-group"); 339 me._speedTapeGroup.setTranslation(ISFD.hsiLeft, 0); 340 me._speedTapeGroup.set('z-index', 2); 341 342 var tapePath = me._speedTapeGroup.createChild("path", "speed-tape"); 343 tapePath.setStrokeLineWidth(2); 344 tapePath.setColor(1, 1, 1); 345 346 var knotSpacing = 4; 347 var tenKnotWidth = 16; 348 var twentyKnotWidth = 8; 349 350 for (var i=0; i<=25; i+=1) 351 { 352 var tenKnotY = ((i * 20) + 10) * knotSpacing; 353 var twentyKnot = ((i+1) * 20); 354 355 tapePath.moveTo(0, -tenKnotY).line(-tenKnotWidth, 0); 356 tapePath.moveTo(0, -twentyKnot * knotSpacing).line(-twentyKnotWidth, 0); 357 358 var text = me._speedTapeGroup.createChild("text", "speed-tape-legend-" ~ twentyKnot); 359 text.setText(twentyKnot); 360 text.setAlignment("right-center"); 361 text.setFont("LiberationFonts/LiberationMono-Bold.ttf"); 362 text.setFontSize(36); 363 text.setTranslation(-twentyKnotWidth-2, -twentyKnot * knotSpacing); 364 } 365}, 366 367updateSpeedTape : func() 368{ 369 # special case when speed is close to zero (show 'bottom' only) 370 # translate based on start value 371 # update digits in text 372 373 # given it's a 100kt range, maybe fixed graphic for the tape reduces updates? 374 375 var yOffset = 4 * me._controller.getIndicatedAirspeedKnots(); 376 me._speedTapeGroup.setTranslation(ISFD.hsiLeft, yOffset); 377}, 378 379createAltitudeTape : func() 380{ 381 # tick every 50' interval 382 # text, monospace, 5 digits, ever 100' 383 384 # background box 385 var box = me.root.rect(ISFD.hsiRight + 1, -ISFD.halfSize, 386 ISFD.altTapeWidth, ISFD.baseSize); 387 box.setColorFill('#738A7E'); 388 box.set('z-index', -1); 389 390 me._altTapeGroup = me.root.createChild("group", "altitude-tape-group"); 391 me._altTapeGroup.setTranslation(ISFD.hsiRight, 0); 392 393 var tapePath = me._altTapeGroup.createChild("path", "altitude-tape"); 394 tapePath.setStrokeLineWidth(2); 395 tapePath.setColor(1, 1, 1); 396 397 var hundredFtSpacing = 64; 398 var twoHundredFtSpacing = hundredFtSpacing * 2; 399 var twoHundredFtWidth = 20; 400 401 # empty array to hold altitude tape texts for easy updating 402 me._altitudeTapeTexts = []; 403 404 for (var i=0; i<=12; i+=1) 405 { 406 var y = (i - 6) * twoHundredFtSpacing; 407 var tickY = y + hundredFtSpacing; 408 409 tapePath.moveTo(0, -tickY).line(twoHundredFtWidth, 0); 410 411 var text = me._altTapeGroup.createChild("text", "altitude-tape-legend-" ~ i); 412 text.setText(text); 413 text.setFontSize(36); 414 text.setAlignment("left-center"); 415 text.setFont("LiberationFonts/LiberationMono-Regular.ttf"); 416 text.setTranslation(2, -y); 417 418 # we will update the text very often, ensure we only do 419 # real work if it actually changes 420 text.enableUpdate(); 421 422 # save for later updating 423 append(me._altitudeTapeTexts, text); 424 } 425}, 426 427updateAltitudeTape : func() 428{ 429 var altFt = me._controller.getAltitudeFt(); 430 var alt200 = int(altFt/200); 431 var altMod200 = altFt - (alt200 * 200); 432 433 var offset = 128 * (altMod200 / 200.0); 434 me._altTapeGroup.setTranslation(ISFD.hsiRight, offset); 435 436 # compute this as current alt - half altitude range 437 var lowestAlt = (alt200 - 6) * 200; 438 439 for (var i=0; i<=12; i+=1) 440 { 441 var alt = lowestAlt + (i * 200); 442 # printf with 5 digits 443 var s = sprintf("%05i", alt); 444 me._altitudeTapeTexts[i].updateText(s); 445 } 446 447 # compute transform on group to put the actual altitude on centre 448}, 449 450createCompassRose : func() 451{ 452 453 454 455 # clip group for numerals 456 var clipGroup = me.root.createChild("group", "rose-clip-group"); 457 clipGroup.set("clip-frame", canvas.Element.LOCAL); 458 clipGroup.set("clip", "rect(176px, 142px, 256px, -164px)"); 459 clipGroup.set('z-index', 2); 460 461 var roseBoxHeight = ISFD.modeBoxHeight; 462 var hh = roseBoxHeight / 2; 463 464 # background of the compass 465 var p = clipGroup.createChild('path', 'rose-background'); 466 p.moveTo(ISFD.hsiXCenter - ISFD.roseRadius, ISFD.hsiBottom + 12 + ISFD.roseRadius); 467 p.arcSmallCW(ISFD.roseRadius, ISFD.roseRadius, 0, 468 ISFD.roseRadius * 2, 0); 469 p.close(); 470 p.setColorFill('#738A7E'); 471 472 # add path for the heading arrow 473 var arrow = me.root.createChild("path", "rose-arrow"); 474 arrow.setTranslation(ISFD.hsiXCenter, ISFD.hsiBottom); 475 arrow.setStrokeLineWidth(2); 476 arrow.setColor(1, 1, 1); 477 478 # same size as the roll arrow 479 var arrowHeight = (ISFD.hsiHeight / 2) - ISFD.rollBaseRadius; 480 var arrowHWidth = arrowHeight * (2/3); 481 482 arrow.moveTo(0, arrowHeight); 483 arrow.line(arrowHWidth, -arrowHeight); 484 arrow.line(-arrowHWidth * 2, 0); 485 arrow.close(); 486 arrow.set('z-index', 4); 487 488 me._roseGroup = clipGroup.createChild('group', 'rose-group'); 489 me._roseGroup.setTranslation(ISFD.hsiXCenter, ISFD.hsiBottom + 12 + ISFD.roseRadius); 490 491 var roseTicks = me._roseGroup.createChild('path', 'rose-ticks'); 492 roseTicks.setStrokeLineWidth(2); 493 roseTicks.setColor(1, 1, 1); 494 roseTicks.set('z-index', 2); 495 496 var textR = (ISFD.roseRadius) - 16; 497 for (var i=0; i<36; i+=1) { 498 # create ten degree text 499 # TODO: 30 degree sizes should be bigger 500 501 var text = me._roseGroup.createChild("text", "compass-rose-" ~ i); 502 text.setText(i); 503 text.setFontSize(36); 504 text.setAlignment("center-top"); 505 text.setFont("LiberationFonts/LiberationMono-Bold.ttf"); 506 507 var horAngle = 90 - (i * 10); # angle from +ve X axis 508 var sa = math.sin(horAngle * D2R); 509 var ca = math.cos(horAngle * D2R); 510 text.setTranslation(ca * textR, -sa * textR); 511 text.setRotation(i * 10 * D2R); 512 513 me.addPolarTick(roseTicks, i * 10, ISFD.roseRadius, ISFD.roseRadius - 8); 514 me.addPolarTick(roseTicks, (i * 10) + 5, ISFD.roseRadius, ISFD.roseRadius - 16); 515 } 516 517 roseTicks.close(); 518 519}, 520 521createAltitudeBox : func() 522{ 523 var halfBoxHeight = ISFD.boxHeight / 2; 524 var box = me.root.rect(ISFD.hsiRight - 10, -halfBoxHeight, 525 ISFD.altTapeWidth + 10, ISFD.boxHeight); 526 box.setColorFill('#000000'); 527 box.setColor('#ffffff'); 528 box.setStrokeLineWidth(2); 529 box.set('z-index', 2); 530 531 var clipGroup = me.root.createChild('group', 'altitude-box-clip'); 532 clipGroup.set("clip-frame", canvas.Element.LOCAL); 533 clipGroup.set("clip", "rect(-24px," ~ (ISFD.altTapeWidth + 10) ~ "px, 24px, 0px)"); 534 clipGroup.setTranslation(ISFD.hsiRight - 10, 0); 535 clipGroup.set('z-index', 3); 536 537 var text = clipGroup.createChild('text', 'altitude-box-text'); 538 me._altitudeBoxText = text; 539 540 text.setText('88 '); 541 text.setFontSize(44); 542 text.setAlignment("left-center"); 543 text.setFont("LiberationFonts/LiberationMono-Regular.ttf"); 544 545 me._altitudeDigits00 = me.createDigitTape(clipGroup, 'altitude-digits00', '0'); 546 me._altitudeDigits00.setFontSize(32); 547 me._altitudeDigits00.set('z-index', 4); 548}, 549 550createSpeedBox : func() 551{ 552 var halfBoxHeight = ISFD.boxHeight / 2; 553 var box = me.root.rect(-ISFD.halfSize - 2, -halfBoxHeight, 554 ISFD.speedTapeWidth + 4, ISFD.boxHeight); 555 box.setColorFill('#000000'); 556 box.setColor('#ffffff'); 557 box.setStrokeLineWidth(2); 558 box.set('z-index', 2); 559 560 var clipGroup = me.root.createChild('group', 'speed-box-clip'); 561 clipGroup.set("clip-frame", canvas.Element.LOCAL); 562 clipGroup.set("clip", "rect(-24px," ~ (ISFD.speedTapeWidth + 4) ~ "px, 24px, 0px)"); 563 clipGroup.setTranslation(-ISFD.halfSize, 0); 564 clipGroup.set('z-index', 3); 565 566 var text = clipGroup.createChild('text', 'speed-box-text'); 567 me._speedBoxText = text; 568 569 text.setText('88 '); 570 text.setFontSize(44); 571 text.setAlignment("left-center"); 572 text.setFont("LiberationFonts/LiberationMono-Regular.ttf"); 573 574 me._speedDigit0 = me.createDigitTape(clipGroup, 'speed-digit0'); 575 me._speedDigit0.set('z-index', 4); 576}, 577 578createAltimeterSetting: func() 579{ 580 me._altimeterText = me.root.createChild('text', 'altimeter-setting-text'); 581 me._altimeterText.setText('1013'); 582 me._altimeterText.setAlignment("right-center"); 583 me._altimeterText.setFont("LiberationFonts/LiberationMono-Regular.ttf"); 584 me._altimeterText.setColor('#00ff00'); 585 586 var midTextY = -ISFD.halfSize + (ISFD.modeBoxHeight * 0.5); 587 me._altimeterText.setTranslation(ISFD.hsiRight - 2, midTextY); 588}, 589 590createModeText : func() 591{ 592 me._modeText = me.root.createChild('text', 'mode-text'); 593 me._modeText.setFontSize(44); 594 me._modeText.setText('APP'); 595 me._modeText.setAlignment("left-center"); 596 me._modeText.setFont("LiberationFonts/LiberationMono-Regular.ttf"); 597 me._modeText.setColor('#00ff00'); 598 599 var midTextY = -ISFD.halfSize + (ISFD.modeBoxHeight * 0.5); 600 me._modeText.setTranslation(ISFD.hsiLeft + 2, midTextY); 601}, 602 603updateApproachMode: func 604{ 605 if (me._appMode != 0) { 606 me._modeText.setText('APP'); 607 } 608 609 me._modeText.setVisible(me._appMode); 610 me._localizerGroup.setVisible(me._appMode); 611 me._glideslopeGroup.setVisible(me._appMode); 612}, 613 614createLocalizer: func 615{ 616 var hw = ISFD.needleBoxSize * 0.5; 617 var g = me.root.createChild('group', 'localizer-group'); 618 g.setTranslation(ISFD.hsiXCenter, 156); 619 me._localizerGroup = g; 620 621 var bkg = g.rect(-hw, 0, ISFD.needleBoxSize, ISFD.needleBoxHeight); 622 bkg.setColorFill('#000000'); 623 624 # markers: white line and hollow dots 625 var m = g.createChild("path", "localizer-marker"); 626 m.setStrokeLineWidth(2); 627 m.setColor(1, 1, 1); 628 m.moveTo(0, 0); 629 m.line(0, ISFD.needleBoxHeight); 630 m.close(); 631 632 var r =( ISFD.needleBoxHeight - 8) * 0.5; 633 var hh = ISFD.needleBoxHeight * 0.5; 634 635 # four dots 636 for (var i = -2; i <= 2; i +=1) { 637 if (i == 0) continue; 638 var x = i * 50; 639 m.moveTo(x - r, hh); 640 m.arcSmallCW(r, r, 0, r * 2, 0); 641 m.arcSmallCW(r, r, 0, -r * 2, 0); 642 m.close(); 643 } 644 645 me._localizerPointer = g.createChild("path", "localizer-pointer"); 646 me._localizerPointer.moveTo(0, hh); 647 me.addDiamond(me._localizerPointer , hh); 648 me._localizerPointer.setColorFill('#ff00ff'); # magenta! 649}, 650 651createGlideslope: func 652{ 653 var g = me.root.createChild('group', 'glideslope-bar'); 654 me._glideslopeGroup = g; 655 656 var hh = ISFD.needleBoxSize * 0.5; 657 var hw = ISFD.needleBoxHeight * 0.5; 658 659 g.setTranslation(ISFD.hsiRight - 36, 0); 660 me._localizerGroup = g; 661 662 var bkg = g.rect(0, -hh, ISFD.needleBoxHeight, ISFD.needleBoxSize); 663 bkg.setColorFill('#000000'); 664 665 # markers: white line and hollow dots 666 var m = g.createChild("path", "gs-marker"); 667 m.setStrokeLineWidth(2); 668 m.setColor(1, 1, 1); 669 m.moveTo(0, 0); 670 m.line(ISFD.needleBoxHeight, 0); 671 m.close(); 672 673 var r =( ISFD.needleBoxHeight - 8) * 0.5; 674 675 # four dots 676 for (var i = -2; i <= 2; i +=1) { 677 if (i == 0) continue; 678 var y = i * 50; 679 m.moveTo(hw, y - r); 680 m.arcSmallCW(r, r, 0, 0, r * 2); 681 m.arcSmallCW(r, r, 0, 0, -r * 2); 682 m.close(); 683 } 684 685 me._gsPointer = g.createChild("path", "gs-pointer"); 686 me._gsPointer.moveTo(hw, 0); 687 me.addDiamond(me._gsPointer , hw); 688 me._gsPointer.setColorFill('#ff00ff'); # magenta! 689}, 690 691updateILS: func 692{ 693 var hsz = ISFD.needleBoxSize * 0.5; 694 695 me._localizerPointer.setVisible(me._controller.isLocalizerValid()); 696 var locDev = me._controller.getLocalizerDeviationNorm() * hsz; 697 me._localizerPointer.setTranslation(locDev, 0); 698 699 me._gsPointer.setVisible(me._controller.isGSValid()); 700 var gsDev = me._controller.getGSDeviationNorm() * hsz; 701 me._gsPointer.setTranslation(0, gsDev); 702}, 703 704pressButtonAPP : func 705{ 706 me._controller.toggleApproachMode(); 707}, 708 709update : func() 710{ 711 # read LOC/GS deviation 712 # read Mach for some options 713 me._controller.update(); 714 715# pitch and roll 716 var roll = me._controller.getBankAngleDeg() * D2R; 717 var pitch = me._controller.getPitchDeg(); 718 me.pitchGroup.setTranslation(ISFD.hsiXCenter, pitch * me.pitchLadderDegreeSpacing); 719 me._pitchRotation.setRotation(-roll); 720 me._rollGroup.setRotation(-roll); 721 722# heading 723 me._roseGroup.setRotation(-me._controller.getHeadingDeg() * D2R); 724 725 me.updateAltitudeTape(); 726 me.updateSpeedTape(); 727 728# speed box 729 var spd = me._controller.getIndicatedAirspeedKnots(); 730 var spdDigit0 = math.mod(spd, 10); 731 me._speedDigit0.setTranslation(53, 16 + spdDigit0 * 44); 732 var s = sprintf("%02i ", math.floor(spd / 10)); 733 me._speedBoxText.setText(s); 734 735# altitude box 736 var alt = me._controller.getAltitudeFt(); 737 var altDigits00 = math.mod(alt / 10, 10); 738 me._altitudeDigits00.setTranslation(80, 16 + altDigits00 * 32); 739 var s = sprintf("%03i ", math.floor(alt / 100)); 740 me._altitudeBoxText.setText(s); 741 742# barometric 743 if (me._controller.isSTDBarometricPressure()) { 744 me._altimeterText.setFontSize(44); 745 me._altimeterText.setText('STD'); 746 } else { 747 var s = ''; 748 if (me._controller.isHPaBarometer()) { 749 s = sprintf('%4d HPA', me._controller.getBarometricPressureSettingHPa()); 750 } else { 751 s = sprintf('%4.2f IN', me._controller.getBarometricPressureSettingInHg()); 752 } 753 754 # smaller text to fit 755 me._altimeterText.setFontSize(32); 756 me._altimeterText.setText(s); 757 } 758 759# APProach mode 760 if (me._appMode != me._controller.isApproachMode()) { 761 me._appMode = me._controller.isApproachMode; 762 me.updateApproachMode(); 763 } 764 765 if (me._appMode != 0) { 766 me.updateILS(); 767 } 768} 769 770}; 771