Major update: fixed fdm-bug (thanks to LesterBoffo!), added tutorials, added replayre...
[fg:toms-fgdata.git] / Aircraft / ec130 / Nasal / ec130.nas
1 # (c) Melchior FRANZ  < mfranz # flightgear : org > Thanks for it- currently there is no better solutionout there!
2
3 print("\x1b[35m
4   ______   _____      __  ____      ___      ____    _  _   
5  |  ____| / ____|    /_ | |___ \    / _ \     |  _ \   | || |  
6  | |__   | |              | |  __) |   | | | |    | |_)|  | || |_ 
7  |  __|  | |              | | |__ <   | | | |    |  _<   |__   _|
8  | |____| |____        | | ___) | | |_| |    | |_) |    | |  
9  |______|\_____|     |_||____/  \___/     |____/    |_|  
10 \x1b[m");
11
12
13 if (!contains(globals, "cprint"))
14         var cprint = func nil;
15
16 var devel = !!getprop("devel");
17 var quickstart = !!getprop("quickstart");
18
19 var sin = func(a) math.sin(a * D2R);
20 var cos = func(a) math.cos(a * D2R);
21 var pow = func(v, w) math.exp(math.ln(v) * w);
22 var npow = func(v, w) v ? math.exp(math.ln(abs(v)) * w) * (v < 0 ? -1 : 1) : 0;
23 var clamp = func(v, min = 0, max = 1) v < min ? min : v > max ? max : v;
24 var normatan = func(x, slope = 1) math.atan2(x, slope) * 2 / math.pi;
25 var bell = func(x, spread = 2) pow(math.e, -(x * x) / spread);
26 var max = func(a, b) a > b ? a : b;
27 var min = func(a, b) a < b ? a : b;
28
29 # liveries =========================================================
30 aircraft.livery.init("Aircraft/ec130/Models/liveries");
31
32 # timers ============================================================
33 aircraft.timer.new("/sim/time/hobbs/helicopter", nil).start();
34
35 # strobes ===========================================================
36 var strobe_switch = props.globals.initNode("controls/lighting/strobe", 1, "BOOL");
37 aircraft.light.new("sim/model/ec130/lighting/strobe-top", [0.05, 1.00], strobe_switch);
38 aircraft.light.new("sim/model/ec130/lighting/strobe-bottom", [0.05, 1.03], strobe_switch);
39
40 # beacons ===========================================================
41 var beacon_switch = props.globals.initNode("controls/lighting/beacon", 1, "BOOL");
42 aircraft.light.new("sim/model/ec130/lighting/beacon-top", [0.62, 0.62], beacon_switch);
43 aircraft.light.new("sim/model/ec130/lighting/beacon-bottom", [0.63, 0.63], beacon_switch);
44
45
46 # nav lights ========================================================
47 var nav_light_switch = props.globals.initNode("controls/lighting/nav-lights", 1, "BOOL");
48 var visibility = props.globals.getNode("environment/visibility-m", 1);
49 var sun_angle = props.globals.getNode("sim/time/sun-angle-rad", 1);
50 var nav_lights = props.globals.getNode("sim/model/ec130/lighting/nav-lights", 1);
51
52 #var nav_light_loop = func {
53 #       if (nav_light_switch.getValue())
54 #               nav_lights.setValue(visibility.getValue() < 5000 or sun_angle.getValue() > 1.4);
55 #       else
56 #               nav_lights.setValue(0);
57 #
58 #       settimer(nav_light_loop, 3);
59 #}
60
61 #nav_light_loop();
62
63
64
65
66
67 # fuel ==============================================================
68
69 # density = 6.682 lb/gal [Flight Manual Section 9.2]
70 # avtur/JET A-1/JP-8
71 var FUEL_DENSITY = getprop("/consumables/fuel/tank/density-ppg"); # pound per gallon
72 var GAL2LB = FUEL_DENSITY;
73 var LB2GAL = 1 / GAL2LB;
74 var KG2GAL = KG2LB * LB2GAL;
75 var GAL2KG = 1 / KG2GAL;
76
77
78
79 var Tank = {
80         new: func(n) {
81                 var m = { parents: [Tank] };
82                 m.capacity = n.getNode("capacity-gal_us").getValue();
83                 m.level_galN = n.initNode("level-gal_us", m.capacity);
84                 m.level_lbN = n.getNode("level-lbs");
85                 m.consume(0);
86                 return m;
87         },
88         level: func {
89                 return me.level_galN.getValue();
90         },
91         consume: func(amount) { # US gal (neg. values for feeding)
92                 var level = me.level();
93                 if (amount > level)
94                         amount = level;
95                 level -= amount;
96                 if (level > me.capacity)
97                         level = me.capacity;
98                 me.level_galN.setDoubleValue(level);
99                 me.level_lbN.setDoubleValue(level * GAL2LB);
100                 return amount;
101         },
102 };
103
104
105
106 var fuel = {
107         init: func {
108                 var fuel = props.globals.getNode("/consumables/fuel");
109                 me.pump_capacity = 6.6 * L2GAL / 60; # same pumps for transfer and supply; from ec135: 6.6 l/min
110                 me.total_galN = fuel.getNode("total-fuel-gals", 1);
111                 me.total_lbN = fuel.getNode("total-fuel-lbs", 1);
112                 me.total_normN = fuel.getNode("total-fuel-norm", 1);
113                 #me.supply = Tank.new(fuel.getNode("tank[1]"));
114                 me.main = Tank.new(fuel.getNode("tank[0]"));
115
116                 #var sw = props.globals.getNode("/controls/switches");
117                 #setlistener(sw.initNode("fuel/transfer-pump[0]", 1, "BOOL"), func(n) me.trans1 = n.getValue(), 1);
118                 #setlistener(sw.initNode("fuel/transfer-pump[1]", 1, "BOOL"), func(n) me.trans2 = n.getValue(), 1);
119                 setlistener("/sim/freeze/fuel", func(n) me.freeze = n.getBoolValue(), 1);
120                 me.capacity = me.main.capacity;
121                 #me.warntime = 0;
122                 #me.update(0);
123         },
124         update: func(dt) {
125                 # transfer pumps (feed supply from main)
126                 #var free = me.supply.capacity - me.supply.level();
127                 #if (free > 0) {
128                 #       var trans_flow = (me.trans1 + me.trans2) * me.pump_capacity;
129                 #       me.supply.consume(-me.main.consume(min(trans_flow * dt, free)));
130                 #}
131
132                 # low fuel warning [POH "General Description" 0.28a]
133                 #var time = elapsedN.getValue();
134                 #if (time > me.warntime and me.supply.level() * GAL2KG < 60) {
135                 #       screen.log.write("LOW FUEL WARNING", 1, 0, 0);
136                 #       me.warntime = time + screen.log.autoscroll * 2;
137                 #}
138
139                 var level = me.main.level();
140                 me.total_galN.setDoubleValue(level);
141                 me.total_lbN.setDoubleValue(level * GAL2LB);
142                 me.total_normN.setDoubleValue(level / me.capacity);
143         },
144         level: func {
145                 return me.main.level();
146         },
147         consume: func(amount) {
148                 return me.freeze ? 0 : me.main.consume(amount);
149         }
150 };
151
152
153
154 # engines/rotor =====================================================
155 var rotor_rpm = props.globals.getNode("rotors/main/rpm");
156 var torque = props.globals.getNode("rotors/gear/total-torque", 1);
157 var collective = props.globals.getNode("controls/engines/engine[0]/throttle");
158 var turbine = props.globals.getNode("sim/model/ec130/turbine-rpm-pct", 1);
159 var torque_pct = props.globals.getNode("sim/model/ec130/torque-pct", 1);
160 var target_rel_rpm = props.globals.getNode("controls/rotor/reltarget", 1);
161 var max_rel_torque = props.globals.getNode("controls/rotor/maxreltorque", 1);
162
163
164
165 var Engine = {
166         new: func(n) {
167                 var m = { parents: [Engine] };
168                 m.in = props.globals.getNode("controls/engines", 1).getChild("engine", n, 1);
169                 m.out = props.globals.getNode("engines", 1).getChild("engine", n, 1);
170                 m.airtempN = props.globals.getNode("/environment/temperature-degc");
171
172                 # input
173                 m.ignitionN = m.in.initNode("ignition", 0, "BOOL");
174                 m.starterN = m.in.initNode("starter", 0, "BOOL");
175                 m.powerN = m.in.initNode("power", 0);
176                 m.magnetoN = m.in.initNode("magnetos", 1, "INT");
177
178                 # output
179                 m.runningN = m.out.initNode("running", 0, "BOOL");
180                 m.n1_pctN = m.out.initNode("n1-pct", 0);
181                 m.n2_pctN = m.out.initNode("n2-pct", 0);
182                 m.n1N = m.out.initNode("n1-rpm", 0);
183                 m.n2N = m.out.initNode("n2-rpm", 0);
184                 m.totN = m.out.initNode("tot-degc", m.airtempN.getValue());
185
186                 m.starterLP = aircraft.lowpass.new(3);
187                 m.n1LP = aircraft.lowpass.new(4);
188                 m.n2LP = aircraft.lowpass.new(4);
189                 setlistener("/sim/signals/reinit", func(n) n.getValue() or m.reset(), 1);
190                 m.timer = aircraft.timer.new("/sim/time/hobbs/turbines[" ~ n ~ "]", 10);
191                 m.running = 0;
192                 m.fuelflow = 0;
193                 m.n1 = -1;
194                 m.up = -1;
195                 return m;
196         },
197         reset: func {
198                 me.magnetoN.setIntValue(1);
199                 me.ignitionN.setBoolValue(0);
200                 me.starterN.setBoolValue(0);
201                 me.powerN.setDoubleValue(0);
202                 me.runningN.setBoolValue(me.running = 0);
203                 me.starterLP.set(0);
204                 me.n1LP.set(me.n1 = 0);
205                 me.n2LP.set(me.n2 = 0);
206         },
207         update: func(dt, trim = 0) {
208                 var starter = me.starterLP.filter(me.starterN.getValue() * 0.19);       # starter 15-20% N1max
209                 me.powerN.setValue(me.power = clamp(me.powerN.getValue()));
210                 var power = me.power * 0.97 + trim;                                     # 97% = N2% in flight position
211
212                 if (me.running)
213                         power += (1 - collective.getValue()) * 0.03;                    # droop compensator
214                 if (power > 1.12)
215                         power = 1.12;                                                   # overspeed restrictor
216
217                 me.fuelflow = 0;
218                 if (!me.running) {
219                         if (me.n1 > 0.05 and power > 0.05 and me.ignitionN.getValue()) {
220                                 me.runningN.setBoolValue(me.running = 1);
221                                 me.timer.start();
222                         }
223
224                 } elsif (power < 0.05 or !fuel.level()) {
225                         me.runningN.setBoolValue(me.running = 0);
226                         me.timer.stop();
227
228                 } else {
229                         me.fuelflow = power;
230                 }
231
232                 var lastn1 = me.n1;
233                 me.n1 = me.n1LP.filter(max(me.fuelflow, starter));
234                 me.n2 = me.n2LP.filter(me.n1);
235                 me.up = me.n1 - lastn1;
236
237                 # temperature
238                 if (me.fuelflow > me.pos.idle)
239                         var target = 440 + (779 - 440) * (0.03 + me.fuelflow - me.pos.idle) / (me.pos.flight - me.pos.idle);
240                 else
241                         var target = 440 * (0.03 + me.fuelflow) / me.pos.idle;
242
243                 if (me.n1 < 0.4 and me.fuelflow - me.n1 > 0.001) {
244                         target += (me.fuelflow - me.n1) * 7000;
245                         if (target > 865)
246                                 target = 865;
247                 }
248
249                 var airtemp = me.airtempN.getValue();
250                 if (target < airtemp)
251                         target = airtemp;
252
253                 var decay = (me.up > 0 ? 10 : me.n1 > 0.02 ? 0.01 : 0.001) * dt;
254                 me.totN.setValue((me.totN.getValue() + decay * target) / (1 + decay));
255
256                 # constant 130 kg/h for now (one turbines)
257                 fuel.consume(65 * KG2GAL * me.fuelflow * dt / 3600);
258
259                 # derived gauge values
260                 me.n1_pctN.setDoubleValue(me.n1 * 100);
261                 me.n2_pctN.setDoubleValue(me.n2 * 100);
262                 me.n1N.setDoubleValue(me.n1 * 50970);
263                 me.n2N.setDoubleValue(me.n2 * 33290);
264         },
265         setpower: func(v) {
266                 var target = (int((me.power + 0.15) * 3) + v) / 3;
267                 var time = abs(me.power - target) * 4;
268                 interpolate(me.powerN, target, time);
269         },
270         adjust_power: func(delta, mode = 0) {
271                 if (delta) {
272                         var power = me.powerN.getValue();
273                         if (me.power_min == nil) {
274                                 if (delta > 0) {
275                                         if (power < me.pos.idle) {
276                                                 me.power_min = me.pos.idle;
277                                                 me.power_max = me.pos.flight;
278                                         } else {
279                                                 me.power_min = me.pos.idle;
280                                                 me.power_max = me.pos.flight;
281                                         }
282                                 } else {
283                                         if (power > me.pos.idle) {
284                                                 me.power_max = me.pos.flight;
285                                                 me.power_min = me.pos.idle;
286                                         } else {
287                                                 me.power_max = me.pos.flight;
288                                                 me.power_min = me.pos.idle;
289                                         }
290                                 }
291                         }
292                         me.powerN.setValue(power = clamp(power + delta, me.power_min, me.power_max));
293                         return power;
294                 } elsif (mode) {
295                         me.power_min = me.power_max = nil;
296                 }
297         },
298         pos: { cutoff: 0.0, idle: 0.63, flight: 1 },
299 };
300
301
302
303 var engines = {
304         init: func {
305                 me.engine = [Engine.new(0), Engine.new(1)];
306                 me.trimN = props.globals.initNode("/controls/engines/power-trim");
307                 me.balanceN = props.globals.initNode("/controls/engines/power-balance");
308                 me.commonrpmN = props.globals.initNode("/engines/engine/rpm");
309         },
310         reset: func {
311                 me.engine[0].reset();
312                 me.engine[1].reset();
313         },
314         update: func(dt) {
315
316                 # update engines
317                 var trim = me.trimN.getValue() * 0.1;
318                 var balance = me.balanceN.getValue() * 0.1;
319                 me.engine[0].update(dt, trim - balance);
320                 me.engine[1].update(dt, trim + balance);
321
322                 # set rotor
323                 var n2relrpm =me.engine[0].n2;
324                 var n2max =me.engine[0].n2;
325                 target_rel_rpm.setValue(n2relrpm);
326                 max_rel_torque.setValue(n2max);
327                 
328
329
330                 me.commonrpmN.setValue(n2max * 33290); # attitude indicator needs pressure
331
332                 
333         },
334         adjust_power: func(delta, mode = 0) {
335                 if (!delta) {
336                         engines.engine[0].adjust_power(0, mode);
337                         engines.engine[1].adjust_power(0, mode);
338                 } else {
339                         var p = [0, 0];
340                         for (var i = 0; i < 2; i += 1)
341                                 if (controls.engines[i].selected.getValue())
342                                         p[i] = engines.engine[i].adjust_power(delta);
343                         gui.popupTip(sprintf("power lever %d%%", 100 * max(p[0], p[1])));
344                 }
345         },
346         quickstart: func { # development only
347                 me.engine[0].n1LP.set(1);
348                 me.engine[0].n2LP.set(1);
349                 
350                 procedure.step = 1;
351                 procedure.next();
352         },
353 };
354
355
356
357
358 var vert_speed_fpm = props.globals.initNode("/velocities/vertical-speed-fpm");
359
360 if (devel) {
361         setprop("/instrumentation/altimeter/setting-inhg", getprop("/environment/pressure-inhg"));
362
363         setlistener("/sim/signals/fdm-initialized", func {
364                 settimer(func {
365                         screen.property_display.x = 760;
366                         screen.property_display.y = 200;
367                         screen.property_display.format = "%.3g";
368                         screen.property_display.add(
369                                 rotor_rpm,
370                                 torque_pct,
371                                 target_rel_rpm,
372                                 max_rel_torque,
373                                 "/controls/engines/power-trim",
374                                 "/controls/engines/power-balance",
375                                 "/consumables/fuel/total-fuel-gals",
376                                 "L",
377                                 engines.engine[0].runningN,
378                                 engines.engine[0].ignitionN,
379                                 "/controls/engines/engine[0]/power",
380                                 engines.engine[0].n1_pctN,
381                                 engines.engine[0].n2_pctN,
382                                 engines.engine[0].totN,
383                                 #engines.engine[0].n1N,
384                                 #engines.engine[0].n2N,
385                                 "R",
386                                 "X",
387                                 "/sim/model/gross-weight-kg",
388                                 "/position/altitude-ft",
389                                 "/position/altitude-agl-ft",
390                                 "/instrumentation/altimeter/indicated-altitude-ft",
391                                 "/environment/temperature-degc",
392                                 vert_speed_fpm,
393                                 "/velocities/airspeed-kt",
394                         );
395                 }, 1);
396         });
397 }
398
399
400
401 var mouse = {
402         init: func {
403                 me.x = me.y = nil;
404                 me.savex = nil;
405                 me.savey = nil;
406                 setlistener("/sim/startup/xsize", func(n) me.centerx = int(n.getValue() / 2), 1);
407                 setlistener("/sim/startup/ysize", func(n) me.centery = int(n.getValue() / 2), 1);
408                 setlistener("/devices/status/mice/mouse/mode", func(n) me.mode = n.getValue(), 1);
409                 setlistener("/devices/status/mice/mouse/button[1]", func(n) {
410                         me.mmb = n.getValue();
411                         if (me.mode)
412                                 return;
413                         if (me.mmb) {
414                                 engines.adjust_power(0.0, 1);
415                                 me.savex = me.x;
416                                 me.savey = me.y;
417                                 gui.setCursor(me.centerx, me.centery, "none");
418                         } else {
419                                 gui.setCursor(me.savex, me.savey, "pointer");
420                         }
421                 }, 1);
422                 setlistener("/devices/status/mice/mouse/x", func(n) me.x = n.getValue(), 1);
423                 setlistener("/devices/status/mice/mouse/y", func(n) me.update(me.y = n.getValue()), 1);
424         },
425         update: func {
426                 if (me.mode or !me.mmb)
427                         return;
428
429                 if (var dy = -me.y + me.centery)
430                         engines.adjust_power(dy * 0.005);
431
432                 gui.setCursor(me.centerx, me.centery);
433         },
434 };
435
436
437
438 var power = func(v) {
439         if (controls.engines[0].selected.getValue())
440                 engines.engine[0].setpower(v);
441         
442 }
443
444
445
446 var startup = func {
447         if (procedure.stage < 0) {
448                 procedure.step = 1;
449                 procedure.next();
450         }
451 }
452
453
454 var shutdown = func {
455         if (procedure.stage > 0) {
456                 procedure.step = -1;
457                 procedure.next();
458         }
459 }
460
461
462 var procedure = {
463         
464         stage: -999,
465         step: nil,
466         loopid: 0,
467         reset: func {
468                 me.loopid += 1;
469                 me.stage = -999;
470                 step = nil;
471                 engines.reset();
472         },
473         next: func(delay = 0) {
474                 if (crashed)
475                         return;
476                 if (me.stage < 0 and me.step > 0 or me.stage > 0 and me.step < 0)
477                         me.stage = 0;
478
479                 settimer(func { me.stage += me.step; me.process(me.loopid) }, delay * !quickstart);
480         },
481         process: func(id) {
482                 id == me.loopid or return;
483                 # startup
484                 if (me.stage == 1 ){             
485                         cprint("", "1: press start button #1 -> spool up turbine #1 to N1 8.6--15%");
486                         engines.engine[0].ignitionN.setValue(1);
487                         engines.engine[0].starterN.setValue(1);
488                         
489
490                 } elsif (me.stage == 2) {
491                 
492                         cprint("", "2: move power lever #1 forward -> fuel injection");
493                         engines.engine[0].powerN.setValue(0.13);
494                         me.next(2.5);
495
496                 } elsif (me.stage == 3) {
497                         cprint("", "3: turbine #1 ignition (wait for EGT stabilization)");
498                         me.next(4.5);
499
500                 } elsif (me.stage == 4) {
501                         cprint("", "4: move power lever #1 to idle position -> engine #1 spools up to N1 63%");
502                         engines.engine[0].powerN.setValue(0.63);
503                         me.next(5);
504
505                 } elsif (me.stage == 5) {
506                         cprint("", "5: release start button #1\n");
507                         engines.engine[0].starterN.setValue(0);
508                         engines.engine[0].ignitionN.setValue(0);
509                         me.next(3);
510
511                 
512                 # shutdown
513                 } elsif (me.stage == -1) {
514                         cprint("", "-1: engines shut down");
515                         engines.engine[0].starterN.setValue(0);
516                         
517                         engines.engine[0].ignitionN.setValue(0);
518                         
519                         engines.engine[0].powerN.setValue(0);
520                         
521                         me.next(40);
522
523                 }
524         },
525 };
526
527
528
529 # torquemeter
530 var torque_val = 0;
531 torque.setDoubleValue(0);
532
533 var update_torque = func(dt) {
534         var f = dt / (0.2 + dt);
535         torque_val = torque.getValue() * f + torque_val * (1 - f);
536         torque_pct.setDoubleValue(torque_val / 5300);
537 }
538
539
540
541 # blade vibration absorber pendulum
542 var pendulum = props.globals.getNode("/sim/model/ec130/absorber-angle-deg", 1);
543 var update_absorber = func {
544         pendulum.setDoubleValue(90 * clamp(abs(rotor_rpm.getValue()) / 90));
545 }
546
547
548
549 var vibration = { # and noise ...
550         init: func {
551                 me.lonN = props.globals.initNode("/rotors/main/vibration/longitudinal");
552                 me.latN = props.globals.initNode("/rotors/main/vibration/lateral");
553                 me.soundN = props.globals.initNode("/sim/sound/vibration");
554                 me.airspeedN = props.globals.getNode("/velocities/airspeed-kt");
555                 me.vertspeedN = props.globals.getNode("/velocities/vertical-speed-fps");
556
557                 me.groundspeedN = props.globals.getNode("/velocities/groundspeed-kt");
558                 me.speeddownN = props.globals.getNode("/velocities/speed-down-fps");
559                 me.angleN = props.globals.initNode("/velocities/descent-angle-deg");
560                 me.dir = 0;
561         },
562         update: func(dt) {
563                 var airspeed = me.airspeedN.getValue();
564                 if (airspeed > 160) { # overspeed vibration
565                         var frequency = 2000 + 500 * rand();
566                         var v = 0.49 + 0.5 * normatan(airspeed - 160, 10);
567                         var intensity = v;
568                         var noise = v * internal;
569
570                 } elsif (airspeed > 30) { # Blade Vortex Interaction (BVI)    8 deg, 65 kts max?
571                         var frequency = rotor_rpm.getValue() * 4 * 60;
572                         var down = me.speeddownN.getValue() * FT2M;
573                         var level = me.groundspeedN.getValue() * NM2M / 3600;
574                         me.angleN.setDoubleValue(var angle = math.atan2(down, level) * R2D);
575                         var speed = math.sqrt(level * level + down * down) * MPS2KT;
576                         angle = bell(angle - 9, 13);
577                         speed = bell(speed - 65, 450);
578                         var v = angle * speed;
579                         var intensity = v * 0.10;
580                         var noise = v * (1 - internal * 0.4);
581
582                 } else { # hover
583                         var rpm = rotor_rpm.getValue();
584                         var frequency = rpm * 4 * 60;
585                         var coll = bell(collective.getValue(), 0.5);
586                         var ias = bell(airspeed, 600);
587                         var vert = bell(me.vertspeedN.getValue() * 0.5, 400);
588                         var rpm = 0.477 + 0.5 * normatan(rpm - 350, 30) * 1.025;
589                         var v = coll * ias * vert * rpm;
590                         var intensity = v * 0.10;
591                         var noise = v * (1 - internal * 0.4);
592                 }
593
594                 me.dir += dt * frequency;
595                 me.lonN.setValue(cos(me.dir) * intensity);
596                 me.latN.setValue(sin(me.dir) * intensity);
597                 me.soundN.setValue(noise);
598         },
599 };
600
601
602
603
604 # sound =============================================================
605
606 # stall sound
607 var stall = props.globals.getNode("rotors/main/stall", 1);
608 var stall_filtered = props.globals.getNode("rotors/main/stall-filtered", 1);
609
610 var stall_val = 0;
611 stall.setDoubleValue(0);
612
613 var update_stall = func(dt) {
614         var s = stall.getValue();
615         if (s < stall_val) {
616                 var f = dt / (0.3 + dt);
617                 stall_val = s * f + stall_val * (1 - f);
618         } else {
619                 stall_val = s;
620         }
621         var c = collective.getValue();
622         stall_filtered.setDoubleValue(stall_val + 0.006 * (1 - c));
623 }
624
625
626
627 # skid slide sound
628 var Skid = {
629         new: func(n) {
630                 var m = { parents: [Skid] };
631                 var soundN = props.globals.getNode("sim/model/ec130/sound", 1).getChild("slide", n, 1);
632                 var gearN = props.globals.getNode("gear", 1).getChild("gear", n, 1);
633
634                 m.compressionN = gearN.getNode("compression-norm", 1);
635                 m.rollspeedN = gearN.getNode("rollspeed-ms", 1);
636                 m.frictionN = gearN.getNode("ground-friction-factor", 1);
637                 m.wowN = gearN.getNode("wow", 1);
638                 m.volumeN = soundN.getNode("volume", 1);
639                 m.pitchN = soundN.getNode("pitch", 1);
640
641                 m.compressionN.setDoubleValue(0);
642                 m.rollspeedN.setDoubleValue(0);
643                 m.frictionN.setDoubleValue(0);
644                 m.volumeN.setDoubleValue(0);
645                 m.pitchN.setDoubleValue(0);
646                 m.wowN.setBoolValue(1);
647                 m.self = n;
648                 return m;
649         },
650         update: func {
651                 me.wow = me.wowN.getValue();
652                 if (me.wow < 0.5)
653                         return me.volumeN.setDoubleValue(0);
654
655                 var rollspeed = abs(me.rollspeedN.getValue());
656                 me.pitchN.setDoubleValue(rollspeed * 0.6);
657
658                 var s = normatan(20 * rollspeed);
659                 var f = clamp((me.frictionN.getValue() - 0.5) * 2);
660                 var c = clamp(me.compressionN.getValue() * 2);
661                 var vol = s * f * c;
662                 me.volumeN.setDoubleValue(vol > 0.1 ? vol : 0);
663                 #if (!me.self) {
664                 #       cprint("33;1", sprintf("S=%0.3f  F=%0.3f  C=%0.3f  >>  %0.3f", s, f, c, s * f * c));
665                 #}
666         },
667 };
668
669 var skids = [];
670 for (var i = 0; i < 4; i += 1)
671         append(skids, Skid.new(i));
672
673
674 var update_slide = func {
675         foreach (var s; skids)
676                 s.update();
677 }
678
679
680 var internal = 1;
681 setlistener("sim/current-view/view-number", func {
682         internal = getprop("sim/current-view/internal");
683 }, 1);
684
685
686 var volume = props.globals.getNode("sim/model/ec130/sound/volume", 1);
687
688 # crash handler =====================================================
689 var crash = func {
690         if (arg[0]) {
691                 # crash
692                 setprop("sim/model/ec130/tail-angle-deg", 35);
693                 setprop("sim/model/ec130/shadow", 0);
694                 setprop("rotors/main/rpm", 0);
695                 setprop("rotors/main/blade[0]/flap-deg", -60);
696                 setprop("rotors/main/blade[1]/flap-deg", -50);
697                 setprop("rotors/main/blade[2]/flap-deg", -40);
698                 #setprop("rotors/main/blade[3]/flap-deg", -30);
699                 setprop("rotors/main/blade[0]/incidence-deg", -30);
700                 setprop("rotors/main/blade[1]/incidence-deg", -20);
701                 setprop("rotors/main/blade[2]/incidence-deg", -50);
702                 #setprop("rotors/main/blade[3]/incidence-deg", -55);
703                 setprop("rotors/tail/rpm", 0);
704                 strobe_switch.setValue(0);
705                 beacon_switch.setValue(0);
706                 nav_light_switch.setValue(0);
707                 engines.engine[0].n2_pctN.setValue(0);
708                 #engines.engine[1].n2_pctN.setValue(0);
709                 torque_pct.setValue(torque_val = 0);
710                 stall_filtered.setValue(stall_val = 0);
711
712         } else {
713                 # uncrash (for replay)
714                 setprop("sim/model/ec130/tail-angle-deg", 0);
715                 setprop("sim/model/ec130/shadow", 1);
716                 
717                 setprop("rotors/tail/rpm", 2219);
718                 setprop("rotors/main/rpm", 442);
719                 for (i = 0; i < 4; i += 1) {
720                         setprop("rotors/main/blade[" ~ i ~ "]/flap-deg", 0);
721                         setprop("rotors/main/blade[" ~ i ~ "]/incidence-deg", 0);
722                 }
723                 strobe_switch.setValue(1);
724                 beacon_switch.setValue(1);
725                 engines.engine[0].n2_pct.setValue(100);
726                 #engines.engine[1].n2_pct.setValue(100);
727         }
728 }
729
730
731
732
733 # "manual" rotor animation for flight data recorder replay ============
734 var rotor_step = props.globals.getNode("sim/model/ec130/rotor-step-deg");
735 var blade1_pos = props.globals.getNode("rotors/main/blade[0]/position-deg", 1);
736 var blade2_pos = props.globals.getNode("rotors/main/blade[1]/position-deg", 1);
737 var blade3_pos = props.globals.getNode("rotors/main/blade[2]/position-deg", 1);
738 var blade4_pos = props.globals.getNode("rotors/main/blade[3]/position-deg", 1);
739 var rotorangle = 0;
740
741 var rotoranim_loop = func {
742         var i = rotor_step.getValue();
743         if (i >= 0.0) {
744                 blade1_pos.setValue(rotorangle);
745                 blade2_pos.setValue(rotorangle + 90);
746                 blade3_pos.setValue(rotorangle + 180);
747                 blade4_pos.setValue(rotorangle + 270);
748                 rotorangle += i;
749                 settimer(rotoranim_loop, 0.1);
750         }
751 }
752
753 var init_rotoranim = func {
754         if (rotor_step.getValue() >= 0.0)
755                 settimer(rotoranim_loop, 0.1);
756 }
757
758
759
760
761
762
763
764
765 # view management ===================================================
766
767 var elapsedN = props.globals.getNode("/sim/time/elapsed-sec", 1);
768 var flap_mode = 0;
769 var down_time = 0;
770 controls.flapsDown = func(v) {
771         if (!flap_mode) {
772                 if (v < 0) {
773                         down_time = elapsedN.getValue();
774                         flap_mode = 1;
775                         dynamic_view.lookat(
776                                         5,     # heading left
777                                         -20,   # pitch up
778                                         0,     # roll right
779                                         0.2,   # right
780                                         0.6,   # up
781                                         0.85,  # back
782                                         0.2,   # time
783                                         55,    # field of view
784                         );
785                 } elsif (v > 0) {
786                         flap_mode = 2;
787                         gui.popupTip("AUTOTRIM", 1e10);
788                         aircraft.autotrim.start();
789                 }
790
791         } else {
792                 if (flap_mode == 1) {
793                         if (elapsedN.getValue() < down_time + 0.2)
794                                 return;
795
796                         dynamic_view.resume();
797                 } elsif (flap_mode == 2) {
798                         aircraft.autotrim.stop();
799                         gui.popdown();
800                 }
801                 flap_mode = 0;
802         }
803 }
804
805
806 # register function that may set me.heading_offset, me.pitch_offset, me.roll_offset,
807 # me.x_offset, me.y_offset, me.z_offset, and me.fov_offset
808 #
809 dynamic_view.register(func {
810         var lowspeed = 1 - normatan(me.speedN.getValue() / 50);
811         var r = sin(me.roll) * cos(me.pitch);
812
813         me.heading_offset =                                             # heading change due to
814                 (me.roll < 0 ? -50 : -30) * r * abs(r);                 #    roll left/right
815
816         me.pitch_offset =                                               # pitch change due to
817                 (me.pitch < 0 ? -50 : -50) * sin(me.pitch) * lowspeed   #    pitch down/up
818                 + 15 * sin(me.roll) * sin(me.roll);                     #    roll
819
820         me.roll_offset =                                                # roll change due to
821                 -15 * r * lowspeed;                                     #    roll
822 });
823
824
825 var adjust_fov = func {
826         var w = getprop("/sim/startup/xsize");
827         var h = getprop("/sim/startup/ysize");
828         var ar = clamp(max(w, h) / min(w, h), 0, 2);
829         var fov = 60 + (ar - (4 / 3)) * 10 / (16 / 9 - 4 / 3);
830         setprop("/sim/view/config/default-field-of-view-deg", fov);
831         if (internal)
832                 setprop("/sim/current-view/config/default-field-of-view-deg", fov);
833 }
834
835 setlistener("/sim/startup/xsize", adjust_fov);
836 setlistener("/sim/startup/ysize", adjust_fov, 1);
837
838
839
840
841
842
843
844 # main() ============================================================
845 var delta_time = props.globals.getNode("/sim/time/delta-sec", 1);
846 var hi_heading = props.globals.getNode("/instrumentation/heading-indicator/indicated-heading-deg", 1);
847 var vertspeed = props.globals.initNode("/velocities/vertical-speed-fps");
848 var gross_weight_lb = props.globals.initNode("/yasim/gross-weight-lbs");
849 var gross_weight_kg = props.globals.initNode("/sim/model/gross-weight-kg");
850 props.globals.getNode("/instrumentation/adf/rotation-deg", 1).alias(hi_heading);
851
852
853 var main_loop = func {
854         props.globals.removeChild("autopilot");
855         if (replay)
856                 setprop("/position/gear-agl-m", getprop("/position/altitude-agl-ft") * 0.3 - 1.2);
857         vert_speed_fpm.setDoubleValue(vertspeed.getValue() * 60);
858         gross_weight_kg.setDoubleValue(gross_weight_lb.getValue() * LB2KG);
859
860
861         var dt = delta_time.getValue();
862         update_torque(dt);
863         update_stall(dt);
864         update_slide();
865
866         update_absorber();
867         fuel.update(dt);
868         engines.update(dt);
869         vibration.update(dt);
870         settimer(main_loop, 0);
871 }
872
873
874 var replay = 0;
875 var crashed = 0;
876
877
878 #var config_dialog = gui.Dialog.new("/sim/gui/dialogs/ec130/config/dialog", "Aircraft/ec130/Dialogs/config.xml");
879
880
881 setlistener("/sim/signals/fdm-initialized", func {
882         gui.menuEnable("autopilot", 0);
883         init_rotoranim();
884         vibration.init();
885         engines.init();
886         fuel.init();
887         mouse.init();
888
889         
890         
891
892         collective.setDoubleValue(1);
893
894         setlistener("/sim/signals/reinit", func(n) {
895                 n.getBoolValue() and return;
896                 cprint("32;1", "reinit");
897                 procedure.reset();
898                 collective.setDoubleValue(1);
899                 aircraft.livery.rescan();
900                 reconfigure();
901                 crashed = 0;
902         });
903
904         setlistener("sim/crashed", func(n) {
905                 cprint("31;1", "crashed ", n.getValue());
906                 engines.engine[0].timer.stop();
907                 engines.engine[1].timer.stop();
908                 if (n.getBoolValue())
909                         crash(crashed = 1);
910         });
911
912         setlistener("/sim/freeze/replay-state", func(n) {
913                 replay = n.getValue();
914                 cprint("33;1", replay ? "replay" : "pause");
915                 if (crashed)
916                         crash(!n.getBoolValue())
917         });
918
919         main_loop();
920         if (devel and quickstart)
921                 engines.quickstart();
922 });
923
924