123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- #
- # Radar Cross-section calculation for radars
- #
- # Main author: Pinto
- #
- # License: GPL 2
- #
- # The file vector.nas needs to be available in namespace 'vector'.
- #
- var TRUE = 1;
- var FALSE = 0;
- var test = func (echoHeading, echoPitch, echoRoll, bearing, frontRCS) {
- var myCoord = geo.aircraft_position();
- var echoCoord = geo.Coord.new(myCoord);
- echoCoord.apply_course_distance(bearing, 1000);#1km away
- echoCoord.set_alt(echoCoord.alt()+1000);#1km higher than me
- print("RCS final: "~getRCS(echoCoord, echoHeading, echoPitch, echoRoll, myCoord, frontRCS));
- };
- var rcs_database = {
- parents: [rcs_oprf_database],
- #REVISION: 2021/04/29
- "YF-16": 5, #higher because earlier blocks had larger RCS
- "F-16CJ": 2, #average
- "f16": 2, #average
- "Mig-29": 6, #guess
- "J-11A": 15, #same as Su-27
- "jaguar": 6, #guess
- "onox-tanker": 100, #guess
- "EF2000": 0.5,
- "brsq": 1.5, #average (multiple sources)
- "FA-18C_Hornet": 3.5, #later Blocks have 1
- "FA-18D_Hornet": 3.5,
- "daVinci_SU-34": 8, #should be less
- "Su-34": 8, #should be less
- "SU-37": 8, #wild guess
- "F-5E-TigerII": 4, #close to actual
- "F-5ENinja": 4, #close to actual
- "f-20A": 2.5, #low end of sources
- "f-20C": 2.5,
- "f-20prototype": 2.5,
- "f-20bmw": 2.5,
- "f-20-dutchdemo": 2.5,
- "MiG-15bis": 6, #guess
- "MiG-21Bison": 3.5,
- "MiG-25": 30, #guess
- "Su-25": 7, #guess
- "G91-R1B": 6, #guess
- "Tu-160-Blackjack": 15,
- # Helis:
- "uh60_Blackhawk": 10, #guess
- "AH-1W": 7, #guess
- "WAH-64_Apache": 5, #guess
- "rah-66": 0.8, #wild guess
- "Gazelle": 7, #guess
- "Westland_Gazelle": 7, #guess
- "AS532-Cougar": 9, #guess
- "Westland_SeaKing-HAR3": 10, #guess
- "Lynx-HMA8": 8, #guess
- "Lynx_Wildcat": 8, #guess
- "Merlin-HM1": 14, #guess
- "OH-58D": 7, #guess
- #Stealth
- "b2-spirit": 0.001, #actual: 0.0001
- "B-2A": 0.001, #actual: 0.0001
- "F-22-Raptor": 0.001, #actual: 0.0001
- "F-35A": 0.0005,
- "F-35B": 0.0005,
- "F-35C": 0.0005,
- "daVinci_F-35A": 0.0005,
- "daVinci_F-35B": 0.0005,
- "F-117": 0.003,
- "T-50": 0.5, #low end of sources
- "u-2s": 0.01,
- "U-2S-model": 0.01,
- };
- var prevVisible = {};
- var lastUpdateTime = {};
- var timeNode = props.globals.getNode("sim/time/elapsed-sec");
- # For 'inRadarRange', decide if the previous RCS test result can be result, or if a new test should be done.
- # If the previous test is more than 'max_refresh_sec' old (resp. less than 'min_refresh_sec'),
- # then a new test is always (resp. never) done.
- # In between these two values, a test is done with probability 'refresh_prob'.
- var refreshRequired = func (contact, min_refresh_sec, max_refresh_sec, refresh_prob) {
- var callsign = contact.get_Callsign();
- if (callsign == nil or !contains(lastUpdateTime, callsign)) return TRUE;
- var update_age = timeNode.getValue() - lastUpdateTime[callsign];
- if (update_age < min_refresh_sec) return FALSE;
- elsif (update_age > max_refresh_sec) return TRUE;
- else return (rand() < refresh_prob);
- }
- var inRadarRange = func (contact, myRadarDistance_nm, myRadarStrength_rcs,
- min_refresh_sec=1, max_refresh_sec=10, refresh_prob=0.05) {
- if (refreshRequired(contact, min_refresh_sec, max_refresh_sec, refresh_prob)) {
- return isInRadarRange(contact, myRadarDistance_nm, myRadarStrength_rcs);
- } else {
- return wasInRadarRange(contact, myRadarDistance_nm, myRadarStrength_rcs);
- }
- }
- var wasInRadarRange = func (contact, myRadarDistance_nm, myRadarStrength_rcs) {
- var sign = contact.get_Callsign();
- if (sign != nil and contains(prevVisible, sign)) {
- return prevVisible[sign];
- } else {
- return isInRadarRange(contact, myRadarDistance_nm, myRadarStrength_rcs);
- }
- }
- var isInRadarRange = func (contact, myRadarDistance_nm, myRadarStrength_rcs) {
- if (contact != nil and contact.get_Coord() != nil) {
- var value = 1;
- call(func {value = targetRCSSignal(contact.get_Coord(), contact.get_model(), contact.get_heading(), contact.get_Pitch(), contact.get_Roll(), geo.aircraft_position(), myRadarDistance_nm*NM2M, myRadarStrength_rcs)},nil, var err = []);
- if (size(err)) {
- foreach(line;err) {
- print(line);
- }
- # open radar for one will make this happen.
- return value;
- }
- var callsign = contact.get_Callsign();
- prevVisible[callsign] = value;
- lastUpdateTime[callsign] = timeNode.getValue();
- return value;
- }
- return 0;
- };
- #most detection ranges are for a target that has an rcs of 5m^2, so leave that at default if not specified by source material
- var targetRCSSignal = func(targetCoord, targetModel, targetHeading, targetPitch, targetRoll, myCoord, myRadarDistance_m, myRadarStrength_rcs = 5) {
- #print(targetModel);
- var target_front_rcs = nil;
- if ( contains(rcs_oprf_database,targetModel) ) {
- target_front_rcs = rcs_oprf_database[targetModel];
- } elsif ( contains(rcs_database,targetModel) ) {
- target_front_rcs = rcs_database[targetModel];
- } else {
- return 1;
- target_front_rcs = rcs_database["default"];
- }
- #print(target_front_rcs," RCS from ", targetModel, " m:", myRadarDistance_m, " rcs:",myRadarStrength_rcs);
- var target_rcs = getRCS(targetCoord, targetHeading, targetPitch, targetRoll, myCoord, target_front_rcs);
- var target_distance = myCoord.direct_distance_to(targetCoord);
- # standard formula
- var currMaxDist = myRadarDistance_m/math.pow(myRadarStrength_rcs/target_rcs, 1/4);
- return currMaxDist > target_distance;
- }
- var getRCS = func (echoCoord, echoHeading, echoPitch, echoRoll, myCoord, frontRCS) {
- var sideRCSFactor = 2.50;
- var rearRCSFactor = 1.75;
- var bellyRCSFactor = 3.50;
- #first we calculate the 2D RCS:
- var vectorToEcho = vector.Math.eulerToCartesian2(myCoord.course_to(echoCoord), vector.Math.getPitch(myCoord,echoCoord));
- var vectorEchoNose = vector.Math.eulerToCartesian3X(echoHeading, echoPitch, echoRoll);
- var vectorEchoTop = vector.Math.eulerToCartesian3Z(echoHeading, echoPitch, echoRoll);
- var view2D = vector.Math.projVectorOnPlane(vectorEchoTop,vectorToEcho);
- #print("top "~vector.Math.format(vectorEchoTop));
- #print("nose "~vector.Math.format(vectorEchoNose));
- #print("view "~vector.Math.format(vectorToEcho));
- #print("view2D "~vector.Math.format(view2D));
- var angleToNose = geo.normdeg180(vector.Math.angleBetweenVectors(vectorEchoNose, view2D)+180);
- #print("horz aspect "~angleToNose);
- var horzRCS = 0;
- if (math.abs(angleToNose) <= 90) {
- horzRCS = extrapolate(math.abs(angleToNose), 0, 90, frontRCS, sideRCSFactor*frontRCS);
- } else {
- horzRCS = extrapolate(math.abs(angleToNose), 90, 180, sideRCSFactor*frontRCS, rearRCSFactor*frontRCS);
- }
- #print("RCS horz "~horzRCS);
- #next we calculate the 3D RCS:
- var angleToBelly = geo.normdeg180(vector.Math.angleBetweenVectors(vectorEchoTop, vectorToEcho));
- #print("angle to belly "~angleToBelly);
- var realRCS = 0;
- if (math.abs(angleToBelly) <= 90) {
- realRCS = extrapolate(math.abs(angleToBelly), 0, 90, bellyRCSFactor*frontRCS, horzRCS);
- } else {
- realRCS = extrapolate(math.abs(angleToBelly), 90, 180, horzRCS, bellyRCSFactor*frontRCS);
- }
- return realRCS;
- };
- var extrapolate = func (x, x1, x2, y1, y2) {
- return y1 + ((x - x1) / (x2 - x1)) * (y2 - y1);
- };
- var getAspect = func (echoCoord, myCoord, echoHeading) {# ended up not using this
- # angle 0 deg = view of front
- var course = echoCoord.course_to(myCoord);
- var heading_offset = course - echoHeading;
- return geo.normdeg180(heading_offset);
- };
|