Crash and stress damage system: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
m (Removed references to Jsbsim.)
m (rename)
Line 1: Line 1:
Inspired by the crash system in the [[Mikoyan-Gurevich_MiG-15|Mig-15]], this system is meant to become generic and usable for all aircraft by adding just a few lines. For now you have to add a file with code though.
#REDIRECT [[Crash and stress damage system]]
 
== Features so far==
* Impact detection and tied into the new failure manager.
* Over-G detection for wing stress.
* Sounds.
 
== Planned features==
Feel free to suggest some in the forum topic linked to at the end of this article.
 
== How to install the current system on an aircraft ==
Copy this code into your aircrafts Nasal folder as crash-and-stress.nas file:
<syntaxhighlight lang="nasal">
#
# A Flightgear JSBSim crash and stress damage system.
#
# Inspired by the crash system in Mig15 by Slavutinsky Victor. And by Hvengel's formula for wingload stress.
#
# Authors: Slavutinsky Victor, Nikolai V. Chr. (Necolatis)
#
#
# Version 0.11
#
# License:
#  GPL 2.0
 
 
var TRUE = 1;
var FALSE = 0;
 
var CrashAndStress = {
# pattern singleton
_instance: nil,
# Get the instance
new: func (gears, stressLimit = nil, wingsFailureModes = nil) {
 
var m = nil;
if(me._instance == nil) {
me._instance = {};
me._instance["parents"] = [CrashAndStress];
 
m = me._instance;
 
m.inService = FALSE;
m.repairing = FALSE;
 
m.exploded = FALSE;
 
m.wingsAttached = TRUE;
m.wingLoadLimitUpper = nil;
m.wingLoadLimitLower = nil;
m._looptimer = maketimer(0, m, m._loop);
 
m.input = {
# trembleOn:  "damage/g-tremble-on",
# trembleMax: "damage/g-tremble-max",
replay:    "sim/replay/replay-state",
lat:        "position/latitude-deg",
lon:        "position/longitude-deg",
alt:        "position/altitude-ft",
altAgl:    "position/altitude-agl-ft",
elev:      "position/ground-elev-ft",
  crackOn:    "damage/sounds/crack-on",
creakOn:    "damage/sounds/creaking-on",
crackVol:  "damage/sounds/crack-volume",
creakVol:  "damage/sounds/creaking-volume",
wCrashOn:  "damage/sounds/water-crash-on",
crashOn:    "damage/sounds/crash-on",
detachOn:  "damage/sounds/detach-on",
explodeOn:  "damage/sounds/explode-on",
};
foreach(var ident; keys(m.input)) {
    m.input[ident] = props.globals.getNode(m.input[ident], 1);
}
 
m.fdm = nil;
 
if(getprop("sim/flight-model") == "jsb") {
m.fdm = jsbSimProp;
} elsif(getprop("sim/flight-model") == "yasim") {
m.fdm = yaSimProp;
} else {
return nil;
}
m.fdm.convert();
m.wowStructure = [];
m.wowGear = [];
 
m.lastMessageTime = 0;
 
 
m._identifyGears(gears);
m.setStressLimit(stressLimit);
m.setWingsFailureModes(wingsFailureModes);
 
m._startImpactListeners();
} else {
m = me._instance;
}
 
return m;
},
# start the system
start: func () {
me.inService = TRUE;
},
# stop the system
stop: func () {
me.inService = FALSE;
},
# return TRUE if in progress
isStarted: func () {
return me.inService;
},
# repair the aircaft
repair: func () {
var failure_modes = FailureMgr._failmgr.failure_modes;
var mode_list = keys(failure_modes);
 
foreach(var failure_mode_id; mode_list) {
FailureMgr.set_failure_level(failure_mode_id, 0);
}
me.wingsAttached = TRUE;
me.exploded = FALSE;
me.lastMessageTime = 0;
me.repairing = TRUE;
var timer = maketimer(10, me, me._finishRepair);
timer.start();
},
# accepts a vector with failure mode IDs, they will fail when wings break off.
setWingsFailureModes: func (modes) {
if(modes == nil) {
modes = [];
}
 
##
    # Returns an actuator object that will set the serviceable property at
    # the given node to zero when the level of failure is > 0.
    # it will also fail additionally failure modes.
 
    var set_unserviceable_cascading = func(path, casc_paths) {
 
        var prop = path ~ "/serviceable";
 
        if (props.globals.getNode(prop) == nil) {
            props.globals.initNode(prop, TRUE, "BOOL");
        } else {
        props.globals.getNode(prop).setValue(TRUE);#in case this gets initialized empty from a recorder signal or MP alias.
        }
 
        return {
            parents: [FailureMgr.FailureActuator],
            mode_paths: casc_paths,
            set_failure_level: func(level) {
                setprop(prop, level > 0 ? 0 : 1);
                foreach(var mode_path ; me.mode_paths) {
                    FailureMgr.set_failure_level(mode_path, level);
                }
            },
            get_failure_level: func { getprop(prop) ? 0 : 1 }
        }
    }
 
    var prop = me.fdm.wingsFailureID;
    var actuator_wings = set_unserviceable_cascading(prop, modes);
    FailureMgr.add_failure_mode(prop, "Main wings", actuator_wings);
},
# set the stresslimit for the main wings
setStressLimit: func (stressLimit = nil) {
if (stressLimit != nil) {
var wingloadMax = stressLimit['wingloadMaxLbs'];
var wingloadMin = stressLimit['wingloadMinLbs'];
var maxG = stressLimit['maxG'];
var minG = stressLimit['minG'];
var weight = stressLimit['weightLbs'];
if(wingloadMax != nil) {
me.wingLoadLimitUpper = wingloadMax;
} elsif (maxG != nil and weight != nil) {
me.wingLoadLimitUpper = maxG * weight;
}
 
if(wingloadMin != nil) {
me.wingLoadLimitLower = wingloadMin;
} elsif (minG != nil and weight != nil) {
me.wingLoadLimitLower = minG * weight;
} elsif (me.wingLoadLimitUpper != nil) {
me.wingLoadLimitLower = -me.wingLoadLimitUpper * 0.4;#estimate for when lower is not specified
}
me._looptimer.start();
} else {
me._looptimer.stop();
}
},
_identifyGears: func (gears) {
var contacts = props.globals.getNode("/gear").getChildren("gear");
 
foreach(var contact; contacts) {
var index = contact.getIndex();
var isGear = me._contains(gears, index);
var wow = contact.getChild("wow");
if (isGear == TRUE) {
append(me.wowGear, wow);
} else {
append(me.wowStructure, wow);
}
}
},
_finishRepair: func () {
me.repairing = FALSE;
},
_isStructureInContact: func () {
foreach(var structure; me.wowStructure) {
if (structure.getBoolValue() == TRUE) {
return TRUE;
}
}
return FALSE;
},
_isGearInContact: func () {
foreach(var gear; me.wowGear) {
if (gear.getBoolValue() == TRUE) {
return TRUE;
}
}
return FALSE;
},
_contains: func (vector, content) {
foreach(var vari; vector) {
if (vari == content) {
return TRUE;
}
}
return FALSE;
},
_startImpactListeners: func () {
ImpactStructureListener.crash = me;
foreach(var structure; me.wowStructure) {
setlistener(structure, func {call(ImpactStructureListener.run, nil, ImpactStructureListener, ImpactStructureListener)},0,0);
}
},
_isRunning: func () {
if (me.inService == FALSE or me.input.replay.getBoolValue() == TRUE or me.repairing == TRUE) {
return FALSE;
}
var time = me.fdm.input.simTime.getValue();
if (time != nil and time > 1) {
return TRUE;
}
return FALSE;
},
_calcGroundSpeed: func () {
var horzSpeed = me.fdm.input.vgFps.getValue();
  var vertSpeed = me.fdm.input.downFps.getValue();
  var realSpeed = math.sqrt((horzSpeed * horzSpeed) + (vertSpeed * vertSpeed));
  realSpeed = me.fdm.fps2kt(realSpeed);
  return realSpeed;
},
_impactDamage: func () {
    var lat = me.input.lat.getValue();
var lon = me.input.lon.getValue();
var info = geodinfo(lat, lon);
var solid = info[1] == nil?TRUE:info[1].solid;
var speed = me._calcGroundSpeed();
 
if (me.exploded == FALSE) {
var failure_modes = FailureMgr._failmgr.failure_modes;
    var mode_list = keys(failure_modes);
    var probability = speed / 200.0;# 200kt will fail everything, 0kt will fail nothing.
 
    # test for explosion
    if(probability > 1.0 and me.fdm.input.fuel.getValue() > 2500) {
    # 200kt+ and fuel in tanks will explode the aircraft on impact.
    me._explodeBegin();
    return;
    }
 
    foreach(var failure_mode_id; mode_list) {
    if(rand() < probability) {
      FailureMgr.set_failure_level(failure_mode_id, 1);
      }
    }
var str = "Aircraft hit "~info[1].names[size(info[1].names)-1]~".";
me._output(str);
} elsif (solid == TRUE) {
var pos= geo.Coord.new().set_latlon(lat, lon);
wildfire.ignite(pos, 1);
}
if(solid == TRUE) {
#print("solid");
me._impactSoundBegin(speed);
} else {
#print("water");
me._impactSoundWaterBegin(speed);
}
},
_impactSoundWaterBegin: func (speed) {
if (speed > 5) {#check if sound already running?
me.input.wCrashOn.setValue(1);
var timer = maketimer(3, me, me._impactSoundWaterEnd);
timer.start();
}
},
_impactSoundWaterEnd: func () {
me.input.wCrashOn.setValue(0);
},
_impactSoundBegin: func (speed) {
if (speed > 5) {
me.input.crashOn.setValue(1);
var timer = maketimer(3, me, me._impactSoundEnd);
timer.start();
}
},
_impactSoundEnd: func () {
me.input.crashOn.setValue(0);
},
_explodeBegin: func() {
me.input.explodeOn.setValue(1);
me.exploded = TRUE;
var failure_modes = FailureMgr._failmgr.failure_modes;
    var mode_list = keys(failure_modes);
 
    foreach(var failure_mode_id; mode_list) {
      FailureMgr.set_failure_level(failure_mode_id, 1);
    }
 
    me._output("Aircraft exploded.", TRUE);
 
var timer = maketimer(3, me, me._explodeEnd);
timer.start();
},
_explodeEnd: func () {
me.input.explodeOn.setValue(0);
},
_stressDamage: func (str) {
me._output("Aircraft damaged: Wings broke off, due to "~str~" G forces.");
me.input.detachOn.setValue(1);
  FailureMgr.set_failure_level(me.fdm.wingsFailureID, 1);
 
me.wingsAttached = FALSE;
var timer = maketimer(3, me, me._stressDamageEnd);
timer.start();
},
_stressDamageEnd: func () {
me.input.detachOn.setValue(0);
},
_output: func (str, override = FALSE) {
var time = me.fdm.input.simTime.getValue();
if (override == TRUE or (time - me.lastMessageTime) > 3) {
me.lastMessageTime = time;
print(str);
screen.log.write(str, 0.7098, 0.5372, 0.0);# solarized yellow
}
},
_loop: func () {
me._testStress();
me._testWaterImpact();
},
_testWaterImpact: func () {
if(me.input.altAgl.getValue() < 0) {
var lat = me.input.lat.getValue();
var lon = me.input.lon.getValue();
var info = geodinfo(lat, lon);
var solid = info[1] == nil?TRUE:info[1].solid;
if(solid == FALSE) {
me._impactDamage();
}
}
},
_testStress: func () {
if (me._isRunning() == TRUE and me.wingsAttached == TRUE) {
var gForce = me.fdm.input.Nz.getValue() == nil?1:me.fdm.input.Nz.getValue();
var weight = me.fdm.input.weight.getValue();
var wingload = gForce * weight;
 
#print("wingload: "~wingload~" max: "~me.wingLoadLimitUpper);
var broken = FALSE;
 
if(wingload < 0) {
broken = me._testWingload(-wingload, -me.wingLoadLimitLower);
if(broken == TRUE) {
me._stressDamage("negative");
}
} else {
broken = me._testWingload(wingload, me.wingLoadLimitUpper);
if(broken == TRUE) {
me._stressDamage("positive");
}
}
} else {
me.input.crackOn.setValue(0);
me.input.creakOn.setValue(0);
#me.input.trembleOn.setValue(0);
}
},
_testWingload: func (wingload, wingLoadLimit) {
if (wingload > (wingLoadLimit * 0.5)) {
#me.input.trembleOn.setValue(1);
var tremble_max = math.sqrt((wingload - (wingLoadLimit * 0.5)) / (wingLoadLimit * 0.5));
#me.input.trembleMax.setValue(1);
 
if (wingload > (wingLoadLimit * 0.75)) {
 
#tremble_max = math.sqrt((wingload - (wingLoadLimit * 0.5)) / (wingLoadLimit * 0.5));
me.input.creakVol.setValue(tremble_max);
me.input.creakOn.setValue(1);
 
if (wingload > (wingLoadLimit * 0.90)) {
me.input.crackOn.setValue(1);
me.input.crackVol.setValue(tremble_max);
if (wingload > wingLoadLimit) {
me.input.crackVol.setValue(1);
me.input.creakVol.setValue(1);
#me.input.trembleMax.setValue(1);
return TRUE;
}
} else {
me.input.crackOn.setValue(0);
}
} else {
me.input.creakOn.setValue(0);
}
} else {
me.input.crackOn.setValue(0);
me.input.creakOn.setValue(0);
#me.input.trembleOn.setValue(0);
}
return FALSE;
},
};
 
 
var ImpactStructureListener = {
crash: nil,
run: func () {
if (crash._isRunning() == TRUE) {
var wow = crash._isStructureInContact();
if (wow == TRUE) {
crash._impactDamage();
}
}
},
};
 
 
# static class
var fdmProperties = {
input: {},
convert: func () {
foreach(var ident; keys(me.input)) {
    me.input[ident] = props.globals.getNode(me.input[ident], 1);
}
},
fps2kt: func (fps) {
return fps * 0.5924838;
},
wingsFailureID: nil,
};
 
var jsbSimProp = {
parents: [fdmProperties],
input: {
weight:    "fdm/jsbsim/inertia/weight-lbs",
fuel:      "fdm/jsbsim/propulsion/total-fuel-lbs",
simTime:    "fdm/jsbsim/simulation/sim-time-sec",
vgFps:      "fdm/jsbsim/velocities/vg-fps",
downFps:    "velocities/down-relground-fps",
Nz:        "fdm/jsbsim/accelerations/Nz",
},
wingsFailureID: "fdm/jsbsim/structural/wings",
};
 
var yaSimProp = {
parents: [fdmProperties],
input: {
weight:    "yasim/gross-weight-lbs",
fuel:      "consumables/fuel/total-fuel-lbs",
simTime:    "sim/time/elapsed-sec",
vgFps:      "velocities/groundspeed-kt",
Nz:        "accelerations/n-z-cg-fps_sec",
},
convert: func () {
call(fdmProperties.convert, [], me);
me.input.downFps = props.Node.new().setValue(0);
},
fps2kt: func (fps) {
return fps;
},
wingsFailureID: "structural/wings",
};
 
 
# TODO:
#
# Loss of inertia if impacting/sliding? Or should the jsb groundcontacts take care of that alone?
# If gears hit something at too high speed the gears should be damaged?
# Make property to control if system active, or method enough?
# Explosion depending on bumpiness and speed when sliding?
# Tie in with damage from Bombable?
# Use galvedro's UpdateLoop framework when it gets merged
 
 
# example uses:
#
# var crashCode = CrashAndStress.new([0,1,2];
#
# var crashCode = CrashAndStress.new([0,1,2], {"weightLbs":30000, "maxG": 12});
#
# var crashCode = CrashAndStress.new([0,1,2,3], {"weightLbs":20000, "maxG": 11, "minG": -5});
#
# var crashCode = CrashAndStress.new([0,1,2], {"wingloadMaxLbs": 90000, "wingloadMinLbs": -45000}, ["controls/flight/aileron", "controls/flight/elevator", "controls/flight/flaps"]);
#
# var crashCode = CrashAndStress.new([0,1,2], {"wingloadMaxLbs":90000}, ["controls/flight/aileron", "controls/flight/elevator", "controls/flight/flaps"]);
#
# Gears parameter must be defined.
# Stress parameter is optional. If minimum wing stress is not defined it will be set to -40% of max wingload stress if that is defined.
# The last optional parameter is a list of failure mode IDs that shall fail when wings detach. They must be defined in the FailureMgr.
#
#
# Remember to add sounds and to add the sound properties as custom signals to the replay recorder.
 
 
# use:
var crashCode = CrashAndStress.new([0,1,2], {"weightLbs":30000, "maxG": 12}, ["controls/gear1", "controls/gear2", "controls/flight/aileron", "controls/flight/elevator", "consumables/fuel/wing-tanks"]);
crashCode.start();
 
# test:
var repair = func {
crashCode.repair();
};
</syntaxhighlight>
 
Notice that you should edit the line underneath '''# use''' to fit your aircraft requirements.
 
In your -set.xml file under nasal tags add
 
<syntaxhighlight lang="xml">
<crash>
  <file>Aircraft/[aircraft name here]/Nasal/crash-and-stress.nas</file>
</crash>
</syntaxhighlight>
 
Add this to your sounds file:
 
<syntaxhighlight lang="xml">
        <aircraft-explode>
  <name>aircraft-explode</name>
  <path>Sounds/aircraft-explode.wav</path>
  <mode>once</mode>
  <condition>
                      <or>
              <equals>
        <property>damage/sounds/explode-on</property>
                <value>1</value>
    </equals>
                        <equals>
                            <property>damage/sounds/detach-on</property>
                            <value>1</value>
                          </equals>
                        </or>
  </condition>
  <volume>
  <factor>1</factor>
  </volume>
  </aircraft-explode>
 
  <aircraft-crash>
  <name>aircraft-crash</name>
  <path>Sounds/aircraft-crash.wav</path>
  <mode>once</mode>
  <condition>
    <equals>
    <property>damage/sounds/crash-on</property>
    <value>1</value>
    </equals>
  </condition>
  <volume>
  <factor>1</factor>
  </volume>
  </aircraft-crash>
 
  <aircraft-water-crash>
  <name>aircraft-water-crash</name>
  <path>Sounds/aircraft-water-crash.wav</path>
  <mode>once</mode>
  <condition>
  <equals>
  <property>damage/sounds/water-crash-on</property>
  <value>1</value>
  </equals>
  </condition>
  <volume>
  <factor>1</factor>
  </volume>
  </aircraft-water-crash>
 
  <aircraft-crack>
  <name>aircraft-crack</name>
  <path>Sounds/aircraft-crack.wav</path>
  <mode>once</mode>
  <condition>
  <property>sim/current-view/internal</property>
  <equals>
  <property>damage/sounds/crack-on</property>
  <value>1</value>
  </equals>
  </condition>
  <volume>
  <property>damage/sounds/crack-volume</property>
  <factor>1</factor>
  </volume>
  </aircraft-crack>
 
  <aircraft-creaking>
  <name>aircraft-creaking</name>
  <path>Sounds/aircraft-creaking.wav</path>
  <mode>looped</mode>
  <condition>
    <property>sim/current-view/internal</property>
  <equals>
  <property>damage/sounds/creaking-on</property>
  <value>1</value>
  </equals>
  </condition>
  <volume>
  <property>damage/sounds/creaking-volume</property>
  <factor>1</factor>
  </volume>
  </aircraft-creaking>
</syntaxhighlight>
 
Add some sounds to the aircraft sound folder that corresponds to the file names used in the above. (you can use those in the Mig15 or Ja-37 folders)
 
Optionally add these signals to the replay recorder:
 
<syntaxhighlight lang="xml">
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/explode-on</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/crash-on</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/water-crash-on</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/crack-on</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">damage/sounds/creaking-on</property>
        </signal>
        <signal>
          <type>float</type>
          <property type="string">damage/sounds/crack-volume</property>
        </signal>
        <signal>
          <type>float</type>
          <property type="string">damage/sounds/creaking-volume</property>
        </signal>
        <signal>
          <type>bool</type>
          <property type="string">fdm/jsbsim/structural/wings/serviceable</property>
        </signal>
</syntaxhighlight>
 
Notice that the last signal is jsbsim specific, the corresponding yasim signal is:
 
<syntaxhighlight lan="nasal">
        <signal>
          <type>bool</type>
          <property type="string">structural/wings/serviceable</property>
        </signal>
</syntaxhighlight>
 
That property is also what you would use to animate wings being detached and/or changes in the aerodynamics.
 
== Performance ==
As it is now you should feel no performance degradation at all from using it.
 
== People volunteering to work on this ==
* Bomber
* Necolatis
 
== Aircrafts that use this system ==
[[Saab JA-37 Viggen]] (version 2.83+)
 
== Related content ==
* [http://forum.flightgear.org/viewtopic.php?f=4&t=24901 Generic crash system for JSBSim aircraft] – Forum topic on the official FlightGear forum.
 
[[Category:Aircraft enhancement]]

Revision as of 18:55, 26 December 2014