rcs.nas 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #
  2. # Radar Cross-section calculation for radars
  3. #
  4. # Main author: Pinto
  5. #
  6. # License: GPL 2
  7. #
  8. # The file vector.nas needs to be available in namespace 'vector'.
  9. #
  10. var TRUE = 1;
  11. var FALSE = 0;
  12. var test = func (echoHeading, echoPitch, echoRoll, bearing, frontRCS) {
  13. var myCoord = geo.aircraft_position();
  14. var echoCoord = geo.Coord.new(myCoord);
  15. echoCoord.apply_course_distance(bearing, 1000);#1km away
  16. echoCoord.set_alt(echoCoord.alt()+1000);#1km higher than me
  17. print("RCS final: "~getRCS(echoCoord, echoHeading, echoPitch, echoRoll, myCoord, frontRCS));
  18. };
  19. var rcs_database = {
  20. parents: [rcs_oprf_database],
  21. #REVISION: 2021/04/29
  22. "YF-16": 5, #higher because earlier blocks had larger RCS
  23. "F-16CJ": 2, #average
  24. "f16": 2, #average
  25. "Mig-29": 6, #guess
  26. "J-11A": 15, #same as Su-27
  27. "jaguar": 6, #guess
  28. "onox-tanker": 100, #guess
  29. "EF2000": 0.5,
  30. "brsq": 1.5, #average (multiple sources)
  31. "FA-18C_Hornet": 3.5, #later Blocks have 1
  32. "FA-18D_Hornet": 3.5,
  33. "daVinci_SU-34": 8, #should be less
  34. "Su-34": 8, #should be less
  35. "SU-37": 8, #wild guess
  36. "F-5E-TigerII": 4, #close to actual
  37. "F-5ENinja": 4, #close to actual
  38. "f-20A": 2.5, #low end of sources
  39. "f-20C": 2.5,
  40. "f-20prototype": 2.5,
  41. "f-20bmw": 2.5,
  42. "f-20-dutchdemo": 2.5,
  43. "MiG-15bis": 6, #guess
  44. "MiG-21Bison": 3.5,
  45. "MiG-25": 30, #guess
  46. "Su-25": 7, #guess
  47. "G91-R1B": 6, #guess
  48. "Tu-160-Blackjack": 15,
  49. # Helis:
  50. "uh60_Blackhawk": 10, #guess
  51. "AH-1W": 7, #guess
  52. "WAH-64_Apache": 5, #guess
  53. "rah-66": 0.8, #wild guess
  54. "Gazelle": 7, #guess
  55. "Westland_Gazelle": 7, #guess
  56. "AS532-Cougar": 9, #guess
  57. "Westland_SeaKing-HAR3": 10, #guess
  58. "Lynx-HMA8": 8, #guess
  59. "Lynx_Wildcat": 8, #guess
  60. "Merlin-HM1": 14, #guess
  61. "OH-58D": 7, #guess
  62. #Stealth
  63. "b2-spirit": 0.001, #actual: 0.0001
  64. "B-2A": 0.001, #actual: 0.0001
  65. "F-22-Raptor": 0.001, #actual: 0.0001
  66. "F-35A": 0.0005,
  67. "F-35B": 0.0005,
  68. "F-35C": 0.0005,
  69. "daVinci_F-35A": 0.0005,
  70. "daVinci_F-35B": 0.0005,
  71. "F-117": 0.003,
  72. "T-50": 0.5, #low end of sources
  73. "u-2s": 0.01,
  74. "U-2S-model": 0.01,
  75. };
  76. var prevVisible = {};
  77. var lastUpdateTime = {};
  78. var timeNode = props.globals.getNode("sim/time/elapsed-sec");
  79. # For 'inRadarRange', decide if the previous RCS test result can be result, or if a new test should be done.
  80. # If the previous test is more than 'max_refresh_sec' old (resp. less than 'min_refresh_sec'),
  81. # then a new test is always (resp. never) done.
  82. # In between these two values, a test is done with probability 'refresh_prob'.
  83. var refreshRequired = func (contact, min_refresh_sec, max_refresh_sec, refresh_prob) {
  84. var callsign = contact.get_Callsign();
  85. if (callsign == nil or !contains(lastUpdateTime, callsign)) return TRUE;
  86. var update_age = timeNode.getValue() - lastUpdateTime[callsign];
  87. if (update_age < min_refresh_sec) return FALSE;
  88. elsif (update_age > max_refresh_sec) return TRUE;
  89. else return (rand() < refresh_prob);
  90. }
  91. var inRadarRange = func (contact, myRadarDistance_nm, myRadarStrength_rcs,
  92. min_refresh_sec=1, max_refresh_sec=10, refresh_prob=0.05) {
  93. if (refreshRequired(contact, min_refresh_sec, max_refresh_sec, refresh_prob)) {
  94. return isInRadarRange(contact, myRadarDistance_nm, myRadarStrength_rcs);
  95. } else {
  96. return wasInRadarRange(contact, myRadarDistance_nm, myRadarStrength_rcs);
  97. }
  98. }
  99. var wasInRadarRange = func (contact, myRadarDistance_nm, myRadarStrength_rcs) {
  100. var sign = contact.get_Callsign();
  101. if (sign != nil and contains(prevVisible, sign)) {
  102. return prevVisible[sign];
  103. } else {
  104. return isInRadarRange(contact, myRadarDistance_nm, myRadarStrength_rcs);
  105. }
  106. }
  107. var isInRadarRange = func (contact, myRadarDistance_nm, myRadarStrength_rcs) {
  108. if (contact != nil and contact.get_Coord() != nil) {
  109. var value = 1;
  110. 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 = []);
  111. if (size(err)) {
  112. foreach(line;err) {
  113. print(line);
  114. }
  115. # open radar for one will make this happen.
  116. return value;
  117. }
  118. var callsign = contact.get_Callsign();
  119. prevVisible[callsign] = value;
  120. lastUpdateTime[callsign] = timeNode.getValue();
  121. return value;
  122. }
  123. return 0;
  124. };
  125. #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
  126. var targetRCSSignal = func(targetCoord, targetModel, targetHeading, targetPitch, targetRoll, myCoord, myRadarDistance_m, myRadarStrength_rcs = 5) {
  127. #print(targetModel);
  128. var target_front_rcs = nil;
  129. if ( contains(rcs_oprf_database,targetModel) ) {
  130. target_front_rcs = rcs_oprf_database[targetModel];
  131. } elsif ( contains(rcs_database,targetModel) ) {
  132. target_front_rcs = rcs_database[targetModel];
  133. } else {
  134. return 1;
  135. target_front_rcs = rcs_database["default"];
  136. }
  137. #print(target_front_rcs," RCS from ", targetModel, " m:", myRadarDistance_m, " rcs:",myRadarStrength_rcs);
  138. var target_rcs = getRCS(targetCoord, targetHeading, targetPitch, targetRoll, myCoord, target_front_rcs);
  139. var target_distance = myCoord.direct_distance_to(targetCoord);
  140. # standard formula
  141. var currMaxDist = myRadarDistance_m/math.pow(myRadarStrength_rcs/target_rcs, 1/4);
  142. return currMaxDist > target_distance;
  143. }
  144. var getRCS = func (echoCoord, echoHeading, echoPitch, echoRoll, myCoord, frontRCS) {
  145. var sideRCSFactor = 2.50;
  146. var rearRCSFactor = 1.75;
  147. var bellyRCSFactor = 3.50;
  148. #first we calculate the 2D RCS:
  149. var vectorToEcho = vector.Math.eulerToCartesian2(myCoord.course_to(echoCoord), vector.Math.getPitch(myCoord,echoCoord));
  150. var vectorEchoNose = vector.Math.eulerToCartesian3X(echoHeading, echoPitch, echoRoll);
  151. var vectorEchoTop = vector.Math.eulerToCartesian3Z(echoHeading, echoPitch, echoRoll);
  152. var view2D = vector.Math.projVectorOnPlane(vectorEchoTop,vectorToEcho);
  153. #print("top "~vector.Math.format(vectorEchoTop));
  154. #print("nose "~vector.Math.format(vectorEchoNose));
  155. #print("view "~vector.Math.format(vectorToEcho));
  156. #print("view2D "~vector.Math.format(view2D));
  157. var angleToNose = geo.normdeg180(vector.Math.angleBetweenVectors(vectorEchoNose, view2D)+180);
  158. #print("horz aspect "~angleToNose);
  159. var horzRCS = 0;
  160. if (math.abs(angleToNose) <= 90) {
  161. horzRCS = extrapolate(math.abs(angleToNose), 0, 90, frontRCS, sideRCSFactor*frontRCS);
  162. } else {
  163. horzRCS = extrapolate(math.abs(angleToNose), 90, 180, sideRCSFactor*frontRCS, rearRCSFactor*frontRCS);
  164. }
  165. #print("RCS horz "~horzRCS);
  166. #next we calculate the 3D RCS:
  167. var angleToBelly = geo.normdeg180(vector.Math.angleBetweenVectors(vectorEchoTop, vectorToEcho));
  168. #print("angle to belly "~angleToBelly);
  169. var realRCS = 0;
  170. if (math.abs(angleToBelly) <= 90) {
  171. realRCS = extrapolate(math.abs(angleToBelly), 0, 90, bellyRCSFactor*frontRCS, horzRCS);
  172. } else {
  173. realRCS = extrapolate(math.abs(angleToBelly), 90, 180, horzRCS, bellyRCSFactor*frontRCS);
  174. }
  175. return realRCS;
  176. };
  177. var extrapolate = func (x, x1, x2, y1, y2) {
  178. return y1 + ((x - x1) / (x2 - x1)) * (y2 - y1);
  179. };
  180. var getAspect = func (echoCoord, myCoord, echoHeading) {# ended up not using this
  181. # angle 0 deg = view of front
  182. var course = echoCoord.course_to(myCoord);
  183. var heading_offset = course - echoHeading;
  184. return geo.normdeg180(heading_offset);
  185. };