hud_math.nas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. # Needs vector.nas
  2. #
  3. # Author: Nikolai V. Chr. (FPI location code adapted from Buccaneer aircraft)
  4. #
  5. # Version 1.07
  6. #
  7. # License: GPL 2.0
  8. var HudMath = {
  9. init: func (acHudUpperLeft, acHudLowerRight, canvasSize, uvUpperLeft_norm, uvLowerRight_norm, parallax) {
  10. # acHudUpperLeft, acHudLowerRight: vectors of size 3 that indicates HUD position in 3D world.
  11. # canvasSize: vector of size 2 that indicates size of canvas.
  12. # uvUpperLeft_norm, uvLowerRight_norm: normalized UV coordinates of the canvas texture.
  13. # parallax: If the whole canvas is set to move with head as if focused on infinity.
  14. #
  15. # Assumptions is that HUD canvas is vertical aligned and centered in the Y axis. (will though to some degree work with slanted HUDs)
  16. # Also assumed that UV ratio is not stretched. So that every texel is perfect square formed. (see above)
  17. # Another assumption is that aircraft bore line is parallel with 3D X axis.
  18. # statics
  19. me.hud3dWidth = acHudLowerRight[1]-acHudUpperLeft[1];
  20. me.hud3dHeight = acHudUpperLeft[2]-acHudLowerRight[2];
  21. me.hud3dTop = acHudUpperLeft[2];
  22. me.hud3dX = (acHudUpperLeft[0]+acHudLowerRight[0])*0.5;#average due to slanted HUDs
  23. me.hud3dXTop = acHudUpperLeft[0];
  24. me.hud3dXBottom = acHudLowerRight[0];
  25. me.canvasWidth = (uvLowerRight_norm[0]-uvUpperLeft_norm[0])*canvasSize[0];
  26. me.canvasHeight = (uvUpperLeft_norm[1]-uvLowerRight_norm[1])*canvasSize[1];
  27. me.pixelPerMeterY= me.canvasHeight / me.hud3dHeight;# for x and y seperate because some HUDs are slanted
  28. me.pixelPerMeterX= me.canvasWidth / me.hud3dWidth;
  29. me.parallax = parallax;
  30. me.originCanvas = [uvUpperLeft_norm[0]*canvasSize[0],(1-uvUpperLeft_norm[1])*canvasSize[1]];
  31. me.slanted = me.hud3dXTop-me.hud3dXBottom > 0.01;
  32. #printf("HUD 3D. width=%.2f height=%.2f x_pos=%.2f",me.hud3dWidth,me.hud3dHeight,me.hud3dX);
  33. #printf("HUD canvas. width=%d height=%d pixelX/meter=%.1f",me.canvasWidth,me.canvasHeight,me.pixelPerMeterX);
  34. me.makeProperties_();
  35. delete(me,"makeProperties_");
  36. me.reCalc(1);
  37. },
  38. reCalc: func (initialization = 0) {
  39. # if view position has moved and you dont use parallax, call this.
  40. #
  41. if (me.slanted) {
  42. # TODO: do init here also
  43. me.length = math.sqrt(me.hud3dHeight*me.hud3dHeight+(me.hud3dXTop-me.hud3dXBottom)*(me.hud3dXTop-me.hud3dXBottom));
  44. me.slantAngle = math.acos((me.length*me.length+me.hud3dHeight*me.hud3dHeight-(me.hud3dXTop-me.hud3dXBottom)*(me.hud3dXTop-me.hud3dXBottom))/(2*me.length*me.hud3dHeight));
  45. me.HorizTopToEye = me.input.viewX.getValue()-me.hud3dXTop;
  46. me.slantAngleOther = (180-90-me.slantAngle*R2D)*D2R;
  47. me.extendedHUDToOverEye = me.HorizTopToEye*math.sin(me.slantAngleOther)/math.sin(me.slantAngle)+(me.hud3dTop - me.input.viewZ.getValue());
  48. me.distanceToBore = me.extendedHUDToOverEye*math.sin(me.slantAngle)/math.sin(me.slantAngleOther);#used
  49. me.pixelPerMeterYSlant = me.canvasHeight/me.length;#used
  50. me.boreSlantedDownFromTopMeter = (me.hud3dTop - me.input.viewZ.getValue())*math.sin(90*D2R)/math.sin(me.slantAngleOther);
  51. me.centerOffsetSlantedMeter = -1*(me.length*0.5-me.boreSlantedDownFromTopMeter);#used (distance from center origin up to bore [negative number])
  52. #printf("len=%.3fm angle=%.1fdeg angle2=%.1fdeg boredist=%.3fm borefromtop=%.3fm offset=%.3fm",me.length,me.slantAngle*R2D,me.slantAngleOther*R2D,me.distanceToBore,me.boreSlantedDownFromTopMeter,me.centerOffsetSlantedMeter);
  53. }
  54. if (initialization) {
  55. # calc Y offset from HUD canvas center origin.
  56. me.centerOffset = -1 * (me.canvasHeight/2 - ((me.hud3dTop - me.input.view0Z.getValue())*me.pixelPerMeterY));#TODO: use originCanvas?
  57. } elsif (!me.parallax) {
  58. # calc Y offset from HUD canvas center origin.
  59. me.centerOffset = -1 * (me.canvasHeight/2 - ((me.hud3dTop - me.input.viewZ.getValue())*me.pixelPerMeterY));
  60. }
  61. },
  62. hudX3d: func (y) {
  63. # hud 3D X pos for slanted HUDs. y is pixelPos from bore.
  64. # only does this for x as Y is less affected by slanting.
  65. return me.extrapolate(y/me.pixelPerMeterYSlant,-me.boreSlantedDownFromTopMeter,me.length-me.boreSlantedDownFromTopMeter,me.hud3dXTop,me.hud3dXBottom);
  66. },
  67. getVertDistSlanted: func (pitch) {
  68. # pixels down from bore on slanted HUD
  69. me.slantDistMeter = me.distanceToBore*math.sin(-pitch*D2R)/math.sin((180+pitch-me.slantAngle*R2D-90)*D2R);
  70. return me.pixelPerMeterYSlant*me.slantDistMeter;
  71. },
  72. getVertDistSlantedFromCenter: func (pitch) {
  73. # pixels down from center origin on slanted HUD
  74. me.slantDistMeter = me.centerOffsetSlantedMeter+(me.distanceToBore*math.sin(-pitch*D2R)/math.sin((180+pitch-me.slantAngle*R2D-90)*D2R));
  75. return me.pixelPerMeterYSlant*me.slantDistMeter;
  76. },
  77. getCenterOrigin: func {
  78. # returns center origin in canvas from origin (0,0)
  79. #
  80. # most methods in the library assumes that your root group has been moved to this position.
  81. return [me.originCanvas[0]+me.canvasWidth*0.5,me.originCanvas[1]+me.canvasHeight*0.5];
  82. },
  83. getBorePos: func {
  84. # returns bore pos in canvas from center origin
  85. return [0,me.centerOffset];
  86. },
  87. getBorePosSlanted: func {
  88. # returns bore pos in canvas from center origin
  89. return [0,me.centerOffsetSlantedMeter*me.pixelPerMeterYSlant];
  90. },
  91. getPosFromCoord: func (gpsCoord, aircraft = nil) {
  92. # return pos in canvas from center origin
  93. if (aircraft== nil) {
  94. me.crft = geo.aircraft_position();
  95. } else {
  96. me.crft = aircraft;
  97. }
  98. me.ptch = vector.Math.getPitch(me.crft,gpsCoord);
  99. me.dst = me.crft.direct_distance_to(gpsCoord);
  100. me.brng = me.crft.course_to(gpsCoord);
  101. me.hrz = math.cos(me.ptch*D2R)*me.dst;
  102. me.vel_gz = -math.sin(me.ptch*D2R)*me.dst;
  103. me.vel_gx = math.cos(me.brng*D2R) *me.hrz;
  104. me.vel_gy = math.sin(me.brng*D2R) *me.hrz;
  105. me.yaw = me.input.hdgTrue.getValue() * D2R;
  106. me.roll = me.input.roll.getValue() * D2R;
  107. me.pitch = me.input.pitch.getValue() * D2R;
  108. me.sy = math.sin(me.yaw); me.cy = math.cos(me.yaw);
  109. me.sr = math.sin(me.roll); me.cr = math.cos(me.roll);
  110. me.sp = math.sin(me.pitch); me.cp = math.cos(me.pitch);
  111. me.vel_bx = me.vel_gx * me.cy * me.cp
  112. + me.vel_gy * me.sy * me.cp
  113. + me.vel_gz * -me.sp;
  114. me.vel_by = me.vel_gx * (me.cy * me.sp * me.sr - me.sy * me.cr)
  115. + me.vel_gy * (me.sy * me.sp * me.sr + me.cy * me.cr)
  116. + me.vel_gz * me.cp * me.sr;
  117. me.vel_bz = me.vel_gx * (me.cy * me.sp * me.cr + me.sy * me.sr)
  118. + me.vel_gy * (me.sy * me.sp * me.cr - me.cy * me.sr)
  119. + me.vel_gz * me.cp * me.cr;
  120. me.dir_y = math.atan2(me.round0_(me.vel_bz), math.max(me.vel_bx, 0.001)) * R2D;
  121. me.dir_x = math.atan2(me.round0_(me.vel_by), math.max(me.vel_bx, 0.001)) * R2D;
  122. me.pos = me.getCenterPosFromDegs(me.dir_x,-me.dir_y);
  123. return [me.pos[0], me.pos[1], me.dir_x,-me.dir_y];
  124. },
  125. getPosFromDegs: func (yaw_deg, pitch_deg) {
  126. # return pos from bore
  127. if (yaw_deg > 89) yaw_deg = 89;
  128. if (yaw_deg < -89) yaw_deg = -89;
  129. if (pitch_deg < -89) pitch_deg = -89;
  130. if (pitch_deg > 89) pitch_deg = 89;
  131. var y = 0;
  132. var x = 0;
  133. if (me.slanted) {
  134. y = me.getVertDistSlanted(pitch_deg);
  135. x = me.pixelPerMeterX*((me.input.viewX.getValue() - me.hudX3d(y)) * math.tan(yaw_deg*D2R));
  136. } else {
  137. y = -me.pixelPerMeterY*((me.input.viewX.getValue() - me.hud3dX) * math.tan(pitch_deg*D2R));
  138. x = me.pixelPerMeterX*((me.input.viewX.getValue() - me.hud3dX) * math.tan(yaw_deg*D2R));
  139. }
  140. return [x,y];
  141. },
  142. getCenterPosFromDegs: func (yaw_deg, pitch_deg) {
  143. # return pos from center origin
  144. if (yaw_deg > 89) yaw_deg = 89;
  145. if (yaw_deg < -89) yaw_deg = -89;
  146. if (pitch_deg < -89) pitch_deg = -89;
  147. if (pitch_deg > 89) pitch_deg = 89;
  148. if (me.slanted) {
  149. var y = me.getVertDistSlanted(pitch_deg);
  150. var x = me.pixelPerMeterX*((me.input.viewX.getValue() - me.hudX3d(y)) * math.tan(yaw_deg*D2R));
  151. return [x,y+me.centerOffsetSlantedMeter*me.pixelPerMeterYSlant];
  152. } else {
  153. var y = -me.pixelPerMeterY*((me.input.viewX.getValue() - me.hud3dX) * math.tan(pitch_deg*D2R));
  154. var x = me.pixelPerMeterX*((me.input.viewX.getValue() - me.hud3dX) * math.tan(yaw_deg*D2R));
  155. return [x,y+me.centerOffset];
  156. }
  157. },
  158. isCanvasPosClamped: func (x,y) {
  159. if (x>me.originCanvas[0]+me.canvasWidth or x<me.originCanvas[0] or y >me.originCanvas[1]+me.canvasHeight or y<me.originCanvas[1]) {
  160. return 1;
  161. }
  162. return 0;
  163. },
  164. isCenterPosClamped: func (x,y) {
  165. x += me.getCenterOrigin()[0];
  166. y += me.getCenterOrigin()[1];
  167. return me.isCanvasPosClamped(x,y);
  168. },
  169. getPosFromPolar: func (meter, angle_deg) {
  170. # return pos from center origin (not tested)
  171. me.xxx = me.pixelPerMeterX * meter * math.sin(angle_deg*D2R);
  172. me.yyy = -me.pixelPerMeterY * meter * math.cos(angle_deg*D2R);
  173. return [me.xxx, me.yyy+me.centerOffset];
  174. },
  175. getPolarFromBorePos: func (x,y) {
  176. me.ll = math.sqrt(x*x+y*y);
  177. if (me.ll != 0) {
  178. me.pipAng = math.atan2(x,-y);
  179. return [me.pipAng,me.ll];# notice is radians
  180. }
  181. return [0,0];
  182. },
  183. getPolarFromCenterPos: func (x,y) {
  184. y -= me.centerOffset;
  185. return me.getPolarFromBorePos(x,y);
  186. },
  187. getFlightPathIndicatorPos: func (clampXmin=-1000,clampYmin=-1000,clampXmax=1000,clampYmax=1000) {
  188. # return pos from canvas center origin
  189. # notice that this gives real flightpath location, not influenced by wind. (use the wind for yasim, as there is an issue with that somehow)
  190. me.vel_gx = me.input.speed_n.getValue();
  191. me.vel_gy = me.input.speed_e.getValue();
  192. me.vel_gz = me.input.speed_d.getValue();
  193. me.yaw = me.input.hdgTrue.getValue() * D2R;
  194. me.roll = me.input.roll.getValue() * D2R;
  195. me.pitch = me.input.pitch.getValue() * D2R;
  196. if (math.sqrt(me.vel_gx *me.vel_gx+me.vel_gy*me.vel_gy+me.vel_gz*me.vel_gz)<15) {
  197. # we are pretty much still, point the vector along axis.
  198. me.vel_gx = math.cos(me.yaw)*1;
  199. me.vel_gy = math.sin(me.yaw)*1;
  200. me.vel_gz = 0;
  201. }
  202. me.sy = math.sin(me.yaw); me.cy = math.cos(me.yaw);
  203. me.sr = math.sin(me.roll); me.cr = math.cos(me.roll);
  204. me.sp = math.sin(me.pitch); me.cp = math.cos(me.pitch);
  205. me.vel_bx = me.vel_gx * me.cy * me.cp
  206. + me.vel_gy * me.sy * me.cp
  207. + me.vel_gz * -me.sp;
  208. me.vel_by = me.vel_gx * (me.cy * me.sp * me.sr - me.sy * me.cr)
  209. + me.vel_gy * (me.sy * me.sp * me.sr + me.cy * me.cr)
  210. + me.vel_gz * me.cp * me.sr;
  211. me.vel_bz = me.vel_gx * (me.cy * me.sp * me.cr + me.sy * me.sr)
  212. + me.vel_gy * (me.sy * me.sp * me.cr - me.cy * me.sr)
  213. + me.vel_gz * me.cp * me.cr;
  214. me.dir_y = math.atan2(me.round0_(me.vel_bz), math.max(me.vel_bx, 0.001)) * R2D;
  215. me.dir_x = math.atan2(me.round0_(me.vel_by), math.max(me.vel_bx, 0.001)) * R2D;
  216. me.pos = me.getCenterPosFromDegs(me.dir_x,-me.dir_y);
  217. me.pos_x = me.clamp(me.pos[0], clampXmin, clampXmax);
  218. me.pos_y = me.clamp(me.pos[1], clampYmin, clampYmax);
  219. return [me.pos_x, me.pos_y];
  220. },
  221. getFlightPathIndicatorPosWind: func (clampXmin=-1000,clampYmin=-1000,clampXmax=1000,clampYmax=1000) {
  222. # return pos from canvas center origin
  223. # notice that this does not give real flightpath location, since wind factors in.
  224. me.dir_y = me.input.alpha.getValue();
  225. me.dir_x = me.input.beta.getValue();
  226. if (me.dir_x==nil or me.dir_y==nil) {
  227. me.pos_x = 0;
  228. me.pos_y = 0;
  229. } else{
  230. me.pos = me.getCenterPosFromDegs(me.dir_x,-me.dir_y);
  231. me.pos_x = me.clamp(me.pos[0], clampXmin, clampXmax);
  232. me.pos_y = me.clamp(me.pos[1], clampYmin, clampYmax);
  233. }
  234. return [me.pos_x, me.pos_y];
  235. },
  236. getStaticHorizon: func (averagePoint_deg = 7.5) {
  237. # get translation and rotation for horizon line, static means not centered around FPI.
  238. # return a vector of 3: translation of main horizon group, rotation of main horizon groups transform, translation of sub horizon group (wherein the line (and pitch ladder) is drawn).
  239. me.rot = -me.input.roll.getValue() * D2R;
  240. return [[0,me.getCenterOffset()],me.rot,[0, me.getPixelPerDegreeAvg(averagePoint_deg)*me.input.pitch.getValue()]];
  241. },
  242. getCenterOffset: func {
  243. if (me.slanted) {
  244. return me.centerOffsetSlantedMeter*me.pixelPerMeterYSlant;
  245. } else {
  246. return me.centerOffset;
  247. }
  248. },
  249. getDynamicHorizon: func (averagePoint_deg = 7.5, xMin=1,xMax=1,yMin=1,yMax=1,drift=1, drift_fix=0.0) {
  250. # get translation and rotation for horizon line, dynamic means centered around FPI.
  251. # the min max values are faction from center to edge of hud to restrict ladder movement.
  252. # should be called after getFlightPathIndicatorPos/getFlightPathIndicatorPosWind.
  253. # return a vector of 3: translation of main horizon group, rotation of main horizon groups transform in radians, translation of sub horizon group (wherein the line (and pitch ladder) is drawn).
  254. me.rot = -me.input.roll.getValue() * D2R;
  255. me.pos_x_clamp = drift?me.clamp(me.pos_x, -xMin*me.canvasWidth*0.5,xMax*me.canvasWidth*0.5):0;
  256. me.pos_y_clamp = drift?me.clamp(me.pos_y, -yMin*me.canvasHeight*0.5,yMax*me.canvasHeight*0.5):drift_fix*me.canvasHeight;
  257. # now figure out how much we move horizon group laterally, to keep FPI in middle of it.
  258. me.pos_y_rel = me.pos_y_clamp - me.getCenterOffset();
  259. me.fpi_polar = me.clamp(math.sqrt(me.pos_x_clamp*me.pos_x_clamp+me.pos_y_rel*me.pos_y_rel),0.0001,10000);
  260. me.inv_angle = me.clamp(-me.pos_y_rel/me.fpi_polar,-1,1);
  261. me.fpi_angle = math.acos(me.inv_angle);
  262. if (me.pos_x_clamp < 0) {
  263. me.fpi_angle *= -1;
  264. }
  265. me.fpi_pos_rel_x = math.sin(me.fpi_angle-me.rot)*me.fpi_polar;
  266. return [[0,me.getCenterOffset()],me.rot,[me.fpi_pos_rel_x, me.getPixelPerDegreeAvg(averagePoint_deg)*me.input.pitch.getValue()]];
  267. },
  268. getPixelPerDegreeAvg: func (averagePoint_deg = 7.5) {
  269. # return average value, not exact unless parameter match what you multiply it with.
  270. # the parameter is distance from bore. Typically if the result are to be multiplied on multiple values, use halfway between center and edge of HUD.
  271. # not slant compatiple yet
  272. if (averagePoint_deg == 0) {
  273. averagePoint_deg = 0.001;
  274. }
  275. return 0.5*(me.pixelPerMeterX+me.pixelPerMeterY)*(((me.input.viewX.getValue() - me.hud3dX) * math.tan(averagePoint_deg*D2R))/averagePoint_deg);
  276. },
  277. getPixelPerDegreeXAvg: func (averagePoint_deg = 7.5) {
  278. # return average value, not exact unless parameter match what you multiply it with.
  279. # the parameter is distance from bore. Typically if the result are to be multiplied on multiple values, use halfway between center and edge of HUD.
  280. # not slant compatiple yet
  281. if (averagePoint_deg == 0) {
  282. averagePoint_deg = 0.001;
  283. }
  284. return me.pixelPerMeterX*(((me.input.viewX.getValue() - me.hud3dX) * math.tan(averagePoint_deg*D2R))/averagePoint_deg);
  285. },
  286. getPixelPerDegreeYAvg: func (averagePoint_deg = 7.5) {
  287. # return average value, not exact unless parameter match what you multiply it with.
  288. # the parameter is distance from bore. Typically if the result are to be multiplied on multiple values, use halfway between center and edge of HUD.
  289. # not slant compatiple yet
  290. if (averagePoint_deg == 0) {
  291. averagePoint_deg = 0.001;
  292. }
  293. return me.pixelPerMeterY*(((me.input.viewX.getValue() - me.hud3dX) * math.tan(averagePoint_deg*D2R))/averagePoint_deg);
  294. },
  295. round0_: func(x) {
  296. return math.abs(x) > 0.01 ? x : 0;
  297. },
  298. clamp: func(v, min, max) {
  299. return v < min ? min : v > max ? max : v;
  300. },
  301. extrapolate: func (x, x1, x2, y1, y2) {
  302. return y1 + ((x - x1) / (x2 - x1)) * (y2 - y1);
  303. },
  304. makeProperties_: func {
  305. me.input = {
  306. alpha: "orientation/alpha-deg",
  307. beta: "orientation/side-slip-deg",
  308. hdg: "orientation/heading-magnetic-deg",
  309. hdgTrue: "orientation/heading-deg",
  310. pitch: "orientation/pitch-deg",
  311. roll: "orientation/roll-deg",
  312. speed_d: "velocities/speed-down-fps",
  313. speed_e: "velocities/speed-east-fps",
  314. speed_n: "velocities/speed-north-fps",
  315. viewNumber: "sim/current-view/view-number",
  316. view0Z: "sim/view[0]/config/y-offset-m",
  317. view0X: "sim/view[0]/config/z-offset-m",
  318. viewZ: "sim/current-view/y-offset-m",
  319. viewX: "sim/current-view/z-offset-m",
  320. };
  321. foreach(var name; keys(me.input)) {
  322. me.input[name] = props.globals.getNode(me.input[name], 1);
  323. }
  324. },
  325. };