Crash and stress damage system: Difference between revisions

Jump to navigation Jump to search
m (cat)
Line 10: Line 10:


== How to install the current system on an aircraft ==
== How to install the current system on an aircraft ==
Copy this code into your aircrafts Nasal folder as jsbsim-crash.nas file:
Copy this code into your aircrafts Nasal folder as crash-and-stress.nas file:
<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
#
#
Line 20: Line 20:
#
#
#
#
# Version 0.1
# Version 0.11
#
#
# License:
# License:
Line 29: Line 29:
var FALSE = 0;
var FALSE = 0;


var JsbsimCrash = {
var CrashAndStress = {
# pattern singleton
# pattern singleton
_instance: nil,
_instance: nil,
Line 38: Line 38:
if(me._instance == nil) {
if(me._instance == nil) {
me._instance = {};
me._instance = {};
me._instance["parents"] = [JsbsimCrash];
me._instance["parents"] = [CrashAndStress];


m = me._instance;
m = me._instance;
Line 48: Line 48:


m.wingsAttached = TRUE;
m.wingsAttached = TRUE;
m.loopRunning = FALSE;
m.wingLoadLimitUpper = nil;
m.wingLoadLimitUpper = nil;
m.wingLoadLimitLower = nil;
m.wingLoadLimitLower = nil;
m.wingFailureModes = nil;
m._looptimer = maketimer(0, m, m._loop);


m.input = {
m.input = {
# trembleOn:  "damage/g-tremble-on",
# trembleMax: "damage/g-tremble-max",
replay:    "sim/replay/replay-state",
replay:    "sim/replay/replay-state",
Nz:        "fdm/jsbsim/accelerations/Nz",
lat:        "position/latitude-deg",
lat:        "position/latitude-deg",
lon:        "position/longitude-deg",
lon:        "position/longitude-deg",
Line 61: Line 61:
altAgl:    "position/altitude-agl-ft",
altAgl:    "position/altitude-agl-ft",
elev:      "position/ground-elev-ft",
elev:      "position/ground-elev-ft",
g3d:        "velocities/groundspeed-3D-kt",
simTime:    "fdm/jsbsim/simulation/sim-time-sec",
vgFps:      "fdm/jsbsim/velocities/vg-fps",
  downFps:    "velocities/down-relground-fps",
  crackOn:    "damage/sounds/crack-on",
  crackOn:    "damage/sounds/crack-on",
creakOn:    "damage/sounds/creaking-on",
creakOn:    "damage/sounds/creaking-on",
# trembleOn:  "damage/g-tremble-on",
crackVol:  "damage/sounds/crack-volume",
crackVol:  "damage/sounds/crack-volume",
creakVol:  "damage/sounds/creaking-volume",
creakVol:  "damage/sounds/creaking-volume",
# trembleMax: "damage/g-tremble-max",
wCrashOn:  "damage/sounds/water-crash-on",
wCrashOn:  "damage/sounds/water-crash-on",
crashOn:    "damage/sounds/crash-on",
crashOn:    "damage/sounds/crash-on",
detachOn:  "damage/sounds/detach-on",
detachOn:  "damage/sounds/detach-on",
explodeOn:  "damage/sounds/explode-on",
explodeOn:  "damage/sounds/explode-on",
weight:    "fdm/jsbsim/inertia/weight-lbs",
fuel:      "fdm/jsbsim/propulsion/total-fuel-lbs",
};
};
foreach(var ident; keys(m.input)) {
foreach(var ident; keys(m.input)) {
Line 82: Line 74:
}
}


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.wowStructure = [];
m.wowGear = [];
m.wowGear = [];
Line 123: Line 126:
me.lastMessageTime = 0;
me.lastMessageTime = 0;
me.repairing = TRUE;
me.repairing = TRUE;
settimer(func {call(me._finishRepair, nil, me)}, 10);
var timer = maketimer(10, me, me._finishRepair);
timer.start();
},
},
# accepts a vector with failure mode IDs, they will fail when wings break off.
# accepts a vector with failure mode IDs, they will fail when wings break off.
setWingsFailureModes: func (modes) {
setWingsFailureModes: func (modes) {
me.wingFailureModes = 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
# set the stresslimit for the main wings
setStressLimit: func (stressLimit = nil) {
setStressLimit: func (stressLimit = nil) {
if (stressLimit != nil) {
if (stressLimit != nil) {
var wingloadMax = stressLimit['wingloadMax'];
var wingloadMax = stressLimit['wingloadMaxLbs'];
var wingloadMin = stressLimit['wingloadMin'];
var wingloadMin = stressLimit['wingloadMinLbs'];
var maxG = stressLimit['maxG'];
var maxG = stressLimit['maxG'];
var minG = stressLimit['minG'];
var minG = stressLimit['minG'];
var weight = stressLimit['weight'];
var weight = stressLimit['weightLbs'];
if(wingloadMax != nil) {
if(wingloadMax != nil) {
me.wingLoadLimitUpper = wingloadMax;
me.wingLoadLimitUpper = wingloadMax;
Line 150: Line 188:
me.wingLoadLimitLower = -me.wingLoadLimitUpper * 0.4;#estimate for when lower is not specified
me.wingLoadLimitLower = -me.wingLoadLimitUpper * 0.4;#estimate for when lower is not specified
}
}
me.loopRunning = TRUE;
me._looptimer.start();
me._loop();
} else {
} else {
me.loopRunning = FALSE;
me._looptimer.stop();
}
}
},
},
Line 198: Line 235:
},
},
_startImpactListeners: func () {
_startImpactListeners: func () {
ImpactStructureListener.crash = me;
foreach(var structure; me.wowStructure) {
foreach(var structure; me.wowStructure) {
setlistener(structure, func {call(me._impactStructureListener, nil, me)},0,0);
setlistener(structure, func {call(ImpactStructureListener.run, nil, ImpactStructureListener, ImpactStructureListener)},0,0);
}
}
},
},
_impactGearListener: func () {
_isRunning: func () {
# TODO: damage gear failure mode at high speed impact
if (me.inService == FALSE or me.input.replay.getBoolValue() == TRUE or me.repairing == TRUE) {
},
return FALSE;
_impactStructureListener: func () {
#print("tst: "~me.inService~" "~me.input.replay.getBoolValue()~" "~me._isRunning()~" "~me.repairing);
if (me.inService == TRUE and me.input.replay.getBoolValue() == FALSE and me._isRunning() == TRUE and me.repairing == FALSE) {
#print("testing");
var wow = me._isStructureInContact();
if (wow == TRUE) {
me._impactDamage();
}
}
}
},
var time = me.fdm.input.simTime.getValue();
_isRunning: func () {
var time = me.input.simTime.getValue();
if (time != nil and time > 1) {
if (time != nil and time > 1) {
return TRUE;
return TRUE;
Line 223: Line 251:
},
},
_calcGroundSpeed: func () {
_calcGroundSpeed: func () {
var horzSpeed = me.input.vgFps.getValue();
var horzSpeed = me.fdm.input.vgFps.getValue();
   var vertSpeed = me.input.downFps.getValue();
   var vertSpeed = me.fdm.input.downFps.getValue();
   var realSpeed = math.sqrt((horzSpeed * horzSpeed) + (vertSpeed * vertSpeed));
   var realSpeed = math.sqrt((horzSpeed * horzSpeed) + (vertSpeed * vertSpeed));
   realSpeed = realSpeed * 0.5924838;#ft/s to kt
   realSpeed = me.fdm.fps2kt(realSpeed);
   return realSpeed;
   return realSpeed;
},
},
Line 239: Line 267:
var failure_modes = FailureMgr._failmgr.failure_modes;
var failure_modes = FailureMgr._failmgr.failure_modes;
    var mode_list = keys(failure_modes);
    var mode_list = keys(failure_modes);
    var probability = speed / 200.0;#200kt will fail everything, 0kt will fail nothing.
    var probability = speed / 200.0;# 200kt will fail everything, 0kt will fail nothing.


    #test for explosion
    # test for explosion
    if(probability > 1.0 and me.input.fuel.getValue() > 2500) {
    if(probability > 1.0 and me.fdm.input.fuel.getValue() > 2500) {
    #200kt+ and fuel intanks will explode the aircraft on impact.
    # 200kt+ and fuel in tanks will explode the aircraft on impact.
    me._explodeBegin();
    me._explodeBegin();
    return;
    return;
Line 270: Line 298:
if (speed > 5) {#check if sound already running?
if (speed > 5) {#check if sound already running?
me.input.wCrashOn.setValue(1);
me.input.wCrashOn.setValue(1);
settimer(func {call(me._impactSoundWaterEnd, nil, me)}, 3);
var timer = maketimer(3, me, me._impactSoundWaterEnd);
timer.start();
}
}
},
},
Line 279: Line 308:
if (speed > 5) {
if (speed > 5) {
me.input.crashOn.setValue(1);
me.input.crashOn.setValue(1);
settimer(func {call(me._impactSoundEnd, nil, me)}, 3);
var timer = maketimer(3, me, me._impactSoundEnd);
timer.start();
}
}
},
},
Line 297: Line 327:
    me._output("Aircraft exploded.", TRUE);
    me._output("Aircraft exploded.", TRUE);


settimer(func {call(me._explodeEnd, nil, me)}, 3);
var timer = maketimer(3, me, me._explodeEnd);
timer.start();
},
},
_explodeEnd: func () {
_explodeEnd: func () {
Line 305: Line 336:
me._output("Aircraft damaged: Wings broke off, due to "~str~" G forces.");
me._output("Aircraft damaged: Wings broke off, due to "~str~" G forces.");
me.input.detachOn.setValue(1);
me.input.detachOn.setValue(1);
if (me.wingFailureModes != nil) {
foreach(var failureModeId; me.wingFailureModes) {
  FailureMgr.set_failure_level(me.fdm.wingsFailureID, 1);
      FailureMgr.set_failure_level(failureModeId, 1);
    }
}


me.wingsAttached = FALSE;
me.wingsAttached = FALSE;
settimer(func {call(me._stressDamageEnd, nil, me)}, 3);
var timer = maketimer(3, me, me._stressDamageEnd);
timer.start();
},
},
_stressDamageEnd: func () {
_stressDamageEnd: func () {
Line 318: Line 347:
},
},
_output: func (str, override = FALSE) {
_output: func (str, override = FALSE) {
var time = me.input.simTime.getValue();
var time = me.fdm.input.simTime.getValue();
if (override == TRUE or (time - me.lastMessageTime) > 3) {
if (override == TRUE or (time - me.lastMessageTime) > 3) {
me.lastMessageTime = time;
me.lastMessageTime = time;
Line 328: Line 357:
me._testStress();
me._testStress();
me._testWaterImpact();
me._testWaterImpact();
if(me.loopRunning == TRUE) {
settimer(func {call(me._loop, nil, me)}, 0);
}
},
},
_testWaterImpact: func () {
_testWaterImpact: func () {
Line 344: Line 370:
},
},
_testStress: func () {
_testStress: func () {
if (me.inService == TRUE and me.input.replay.getBoolValue() == FALSE and me._isRunning() == TRUE and me.repairing == FALSE and me.wingsAttached == TRUE) {
if (me._isRunning() == TRUE and me.wingsAttached == TRUE) {
var gForce = me.input.Nz.getValue() == nil?1:me.input.Nz.getValue();
var gForce = me.fdm.input.Nz.getValue() == nil?1:me.fdm.input.Nz.getValue();
var weight = me.input.weight.getValue();
var weight = me.fdm.input.weight.getValue();
var wingload = gForce * weight;
var wingload = gForce * weight;


Line 404: Line 430:
},
},
};
};
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:
# TODO:
Line 412: Line 499:
# Explosion depending on bumpiness and speed when sliding?
# Explosion depending on bumpiness and speed when sliding?
# Tie in with damage from Bombable?
# Tie in with damage from Bombable?
# Use galvedro's UpdateLoop framework when it gets merged




# example uses:
# example uses:
#
#
# var crashCode = JsbsimCrash.new([0,1,2];  
# var crashCode = CrashAndStress.new([0,1,2];  
#
#
# var crashCode = JsbsimCrash.new([0,1,2], {"weight":30000, "maxG": 12}, ["fdm/jsbsim/fcs/wings"]);
# var crashCode = CrashAndStress.new([0,1,2], {"weightLbs":30000, "maxG": 12});
#
#
# var crashCode = JsbsimCrash.new([0,1,2,3], {"weight":20000, "maxG": 11, "minG": -5});
# var crashCode = CrashAndStress.new([0,1,2,3], {"weightLbs":20000, "maxG": 11, "minG": -5});
#
#
# var crashCode = JsbsimCrash.new([0,1,2], {"wingloadMax": 90000, "wingloadMin": -45000}, ["fdm/jsbsim/fcs/wings"]);
# var crashCode = CrashAndStress.new([0,1,2], {"wingloadMaxLbs": 90000, "wingloadMinLbs": -45000}, ["controls/flight/aileron", "controls/flight/elevator", "controls/flight/flaps"]);
#
#
# var crashCode = JsbsimCrash.new([0,1,2], {"wingloadMax":90000}, ["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.
# Gears parameter must be defined.
Line 435: Line 523:


# use:
# use:
var crashCode = JsbsimCrash.new([0,1,2], {"weight":30000, "maxG": 12}, ["fdm/jsbsim/fcs/wings"]);
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();
crashCode.start();


Line 450: Line 538:
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
<crash>
<crash>
   <file>Aircraft/[aircraft name here]/Nasal/jsbsim-crash.nas</file>
   <file>Aircraft/[aircraft name here]/Nasal/crash-and-stress.nas</file>
</crash>
</crash>
</syntaxhighlight>
</syntaxhighlight>
Line 575: Line 663:
           <type>float</type>
           <type>float</type>
           <property type="string">damage/sounds/creaking-volume</property>
           <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>
         </signal>
</syntaxhighlight>
</syntaxhighlight>
That property is also what you would use to animate wings being detached and/or changes in the aerodynamics.


== Performance ==
== Performance ==
574

edits

Navigation menu