From dc697141b8355107acab099c38e2b6bce3630b79 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Mon, 18 Oct 2021 15:27:23 +0200 Subject: [PATCH] Tyre effects (spray, smoke) rework Spray density realism improved by using a curve for the density and permitting the trigger to be changed (default 0.2) Touchdown smoke trigerred by WoW changing using the velocity as a factor and then using a low pass filter on this to fade out the smoke. This results in smoke for a brief period that appears to be more realistic. F-14 example: https://i.imgur.com/FkwgoYV.gifv --- Nasal/aircraft.nas | 129 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 99 insertions(+), 30 deletions(-) diff --git a/Nasal/aircraft.nas b/Nasal/aircraft.nas index f686b3949..3cbffc31d 100644 --- a/Nasal/aircraft.nas +++ b/Nasal/aircraft.nas @@ -811,35 +811,40 @@ var autotrim = { # Note: in reality, tyre smoke doesn't depend on vspeed, but only on acceleration # and friction. # - +# rain_norm_trigger: threshold for deciding that there is enough standing water to +# calculate spray. This is compared against rain-norm. +# var tyresmoke = { - new: func(number, auto = 0, diff_norm = 0.05, check_vspeed=1) { + new: func(number, auto = 0, diff_norm = 0.05, check_vspeed=1, rain_norm_trigger=0.2) { var m = { parents: [tyresmoke] }; m.vertical_speed = (!check_vspeed) ? nil : props.globals.initNode("velocities/vertical-speed-fps"); m.diff_norm = diff_norm; m.speed = props.globals.initNode("velocities/groundspeed-kt"); - m.rain = props.globals.initNode("environment/metar/rain-norm"); - + m.rain_node = props.globals.initNode("environment/metar/rain-norm"); + m.rain_norm_trigger = rain_norm_trigger; var gear = props.globals.getNode("gear/gear[" ~ number ~ "]/"); - m.wow = gear.initNode("wow"); + m.wow_node = gear.initNode("wow"); + m.last_wow = m.wow_node.getValue(); m.tyresmoke = gear.initNode("tyre-smoke", 0, "BOOL"); - m.friction_factor = gear.initNode("ground-friction-factor", 1); + m.friction_factor_node = gear.initNode("ground-friction-factor", 1); m.sprayspeed = gear.initNode("sprayspeed-ms"); m.spray = gear.initNode("spray", 0, "BOOL"); m.spraydensity = gear.initNode("spray-density", 0, "DOUBLE"); m.auto = auto; m.listener = nil; + me.lastwow=0; if (getprop("sim/flight-model") == "jsb") { var wheel_speed = "fdm/jsbsim/gear/unit[" ~ number ~ "]/wheel-speed-fps"; - m.rollspeed = props.globals.initNode(wheel_speed); - m.get_rollspeed = func m.rollspeed.getValue() * 0.3043; + m.rollspeed_node = props.globals.initNode(wheel_speed); + m.get_rollspeed = func m.rollspeed_node.getValue() * 0.3043; } else { - m.rollspeed = gear.initNode("rollspeed-ms"); - m.get_rollspeed = func m.rollspeed.getValue(); + m.rollspeed_node = gear.initNode("rollspeed-ms"); + m.get_rollspeed = func m.rollspeed_node.getValue(); } m.lp = lowpass.new(2); + m.lpf_touchdown = lowpass.new(0.15); auto and m.update(); return m; }, @@ -850,30 +855,93 @@ var tyresmoke = { } me.auto = 0; }, - update: func { - var rollspeed = me.get_rollspeed(); - var vert_speed = (me.vertical_speed) != nil ? me.vertical_speed.getValue() : -999; - var groundspeed = me.speed.getValue(); - var friction_factor = me.friction_factor.getValue(); - var wow = me.wow.getValue(); - var rain = me.rain.getValue(); + calc_spray_factor: func(groundspeed_kts) + { + # based on Figure 31(a)[1]; Variation of drag parameter with ground speed in water + # for dual tandem wheels without spray-drag alleviator curve fitted and normalized. + # + # My The logic here is that the spray will be a factor of the drag and using the + # curve from Figure 31(a) is at least based in reality, whereas previously + # a simple factor was used which tended to give spray at much too low groundspeeds. + # + # | + # | + # 1 | +------------------------------------------------- + # | __/ + # | / + # | / + # | / + # | / + # | / + # | / + # | / + # | / + # | / + # | / + # | / + # | / + # | / + # | _/ + # 0| __-- + # +-------------------------------------------------------------------- + # 0 20 40 60 80 100 120 140 160 180 200 220 240 + # + #_______________________________________________________________________________ + # ref: https://ntrs.nasa.gov/api/citations/19660021919/downloads/19660021919.pdf + #------------------- + # Curve fit: MMF Model: y = (a * b + c * x ^ d) / (b + x ^ d) + # Coefficient Data: a=0.03048 b=59175231 c=2.38119 d=5.08271 + # Normalized by using max value 2.38105217250475 - var filtered_rollspeed = me.lp.filter(rollspeed); - var diff = math.abs(rollspeed - filtered_rollspeed); - var diff_norm = diff > 0 ? diff / rollspeed : 0; + return ((0.03048 * 59175231 + 2.38119 * math.pow(groundspeed_kts, 5.08271)) + / (59175231 + math.pow(groundspeed_kts, 5.08271)) / 2.38105217250475) + ; + }, + update: func { + me.rollspeed = me.get_rollspeed(); + me.vert_speed = (me.vertical_speed) != nil ? me.vertical_speed.getValue() : -999; + me.groundspeed_kts = me.speed.getValue(); + me.friction_factor = me.friction_factor_node.getValue(); + me.wow = me.wow_node.getValue(); + me.rain = me.rain_node.getValue(); - if (wow and vert_speed < -1.2 - and diff_norm > me.diff_norm - and friction_factor > 0.7 and groundspeed > 50 - and rain < 0.20) { + me.filtered_rollspeed = me.lp.filter(me.rollspeed); + me.rollspeed_diff = math.abs(me.rollspeed - me.filtered_rollspeed); + me.rollspeed_diff_norm = me.rollspeed_diff > 0 ? me.rollspeed_diff / me.rollspeed : 0; + me.spray_factor = me.calc_spray_factor(me.groundspeed_kts); + + # touchdown + # - wow changed (previously false) + # - use a filter on touchdown and use this to determine if the smoke is active. + me.touchdown = 0; + if (me.wow != me.lastwow){ + if (me.wow){ + me.lpf_touchdown.set(math.abs(me.vert_speed)); + } + } + else me.filtered_touchdown = me.lpf_touchdown.filter(0); + + # touchdown smoke when + # * recently touched down + # * rollspeed must be over the limit + # * friction must be over a limit (no idea why this is 0.7) + # * moving at ground speed > 50kts (not sure about this) + # * not raining + # possibly using ground speed is somewhat irrelevant - but I'm leaving that here + # as it may filter out unwanted smoke. + if (me.filtered_touchdown > 1.0 + and me.rollspeed_diff_norm > me.diff_norm + and me.friction_factor > 0.7 + and me.groundspeed_kts > 50 + and me.rain 5 and rain >= 0.20) { + } elsif (me.wow and me.rain >= me.rain_norm_trigger) { me.tyresmoke.setValue(0); me.spray.setValue(1); - me.sprayspeed.setValue(rollspeed * 6); - me.spraydensity.setValue(rain * groundspeed); + me.sprayspeed.setValue(me.rollspeed * 6); + me.spraydensity.setValue(me.rain * me.spray_factor * me.groundspeed_kts); } else { me.tyresmoke.setValue(0); me.spray.setValue(0); @@ -881,19 +949,20 @@ var tyresmoke = { me.spraydensity.setValue(0); } if (me.auto) { - if (wow) { + if (me.wow) { settimer(func me.update(), 0); if (me.listener != nil) { removelistener(me.listener); me.listener = nil; } } elsif (me.listener == nil) { - me.listener = setlistener(me.wow, func me._wowchanged_(), 0, 0); + me.listener = setlistener(me.wow_node, func me._wowchanged_(), 0, 0); } } + me.lastwow = me.wow; }, _wowchanged_: func() { - if (me.wow.getValue()) { + if (me.wow_node.getValue()) { me.lp.set(0); me.update(); }