# Needs vector.nas # # Author: Nikolai V. Chr. (FPI location code adapted from Buccaneer aircraft) # # Version 1.07 # # License: GPL 2.0 var HudMath = { init: func (acHudUpperLeft, acHudLowerRight, canvasSize, uvUpperLeft_norm, uvLowerRight_norm, parallax) { # acHudUpperLeft, acHudLowerRight: vectors of size 3 that indicates HUD position in 3D world. # canvasSize: vector of size 2 that indicates size of canvas. # uvUpperLeft_norm, uvLowerRight_norm: normalized UV coordinates of the canvas texture. # parallax: If the whole canvas is set to move with head as if focused on infinity. # # Assumptions is that HUD canvas is vertical aligned and centered in the Y axis. (will though to some degree work with slanted HUDs) # Also assumed that UV ratio is not stretched. So that every texel is perfect square formed. (see above) # Another assumption is that aircraft bore line is parallel with 3D X axis. # statics me.hud3dWidth = acHudLowerRight[1]-acHudUpperLeft[1]; me.hud3dHeight = acHudUpperLeft[2]-acHudLowerRight[2]; me.hud3dTop = acHudUpperLeft[2]; me.hud3dX = (acHudUpperLeft[0]+acHudLowerRight[0])*0.5;#average due to slanted HUDs me.hud3dXTop = acHudUpperLeft[0]; me.hud3dXBottom = acHudLowerRight[0]; me.canvasWidth = (uvLowerRight_norm[0]-uvUpperLeft_norm[0])*canvasSize[0]; me.canvasHeight = (uvUpperLeft_norm[1]-uvLowerRight_norm[1])*canvasSize[1]; me.pixelPerMeterY= me.canvasHeight / me.hud3dHeight;# for x and y seperate because some HUDs are slanted me.pixelPerMeterX= me.canvasWidth / me.hud3dWidth; me.parallax = parallax; me.originCanvas = [uvUpperLeft_norm[0]*canvasSize[0],(1-uvUpperLeft_norm[1])*canvasSize[1]]; me.slanted = me.hud3dXTop-me.hud3dXBottom > 0.01; #printf("HUD 3D. width=%.2f height=%.2f x_pos=%.2f",me.hud3dWidth,me.hud3dHeight,me.hud3dX); #printf("HUD canvas. width=%d height=%d pixelX/meter=%.1f",me.canvasWidth,me.canvasHeight,me.pixelPerMeterX); me.makeProperties_(); delete(me,"makeProperties_"); me.reCalc(1); }, reCalc: func (initialization = 0) { # if view position has moved and you dont use parallax, call this. # if (me.slanted) { # TODO: do init here also me.length = math.sqrt(me.hud3dHeight*me.hud3dHeight+(me.hud3dXTop-me.hud3dXBottom)*(me.hud3dXTop-me.hud3dXBottom)); 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)); me.HorizTopToEye = me.input.viewX.getValue()-me.hud3dXTop; me.slantAngleOther = (180-90-me.slantAngle*R2D)*D2R; me.extendedHUDToOverEye = me.HorizTopToEye*math.sin(me.slantAngleOther)/math.sin(me.slantAngle)+(me.hud3dTop - me.input.viewZ.getValue()); me.distanceToBore = me.extendedHUDToOverEye*math.sin(me.slantAngle)/math.sin(me.slantAngleOther);#used me.pixelPerMeterYSlant = me.canvasHeight/me.length;#used me.boreSlantedDownFromTopMeter = (me.hud3dTop - me.input.viewZ.getValue())*math.sin(90*D2R)/math.sin(me.slantAngleOther); me.centerOffsetSlantedMeter = -1*(me.length*0.5-me.boreSlantedDownFromTopMeter);#used (distance from center origin up to bore [negative number]) #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); } if (initialization) { # calc Y offset from HUD canvas center origin. me.centerOffset = -1 * (me.canvasHeight/2 - ((me.hud3dTop - me.input.view0Z.getValue())*me.pixelPerMeterY));#TODO: use originCanvas? } elsif (!me.parallax) { # calc Y offset from HUD canvas center origin. me.centerOffset = -1 * (me.canvasHeight/2 - ((me.hud3dTop - me.input.viewZ.getValue())*me.pixelPerMeterY)); } }, hudX3d: func (y) { # hud 3D X pos for slanted HUDs. y is pixelPos from bore. # only does this for x as Y is less affected by slanting. return me.extrapolate(y/me.pixelPerMeterYSlant,-me.boreSlantedDownFromTopMeter,me.length-me.boreSlantedDownFromTopMeter,me.hud3dXTop,me.hud3dXBottom); }, getVertDistSlanted: func (pitch) { # pixels down from bore on slanted HUD me.slantDistMeter = me.distanceToBore*math.sin(-pitch*D2R)/math.sin((180+pitch-me.slantAngle*R2D-90)*D2R); return me.pixelPerMeterYSlant*me.slantDistMeter; }, getVertDistSlantedFromCenter: func (pitch) { # pixels down from center origin on slanted HUD me.slantDistMeter = me.centerOffsetSlantedMeter+(me.distanceToBore*math.sin(-pitch*D2R)/math.sin((180+pitch-me.slantAngle*R2D-90)*D2R)); return me.pixelPerMeterYSlant*me.slantDistMeter; }, getCenterOrigin: func { # returns center origin in canvas from origin (0,0) # # most methods in the library assumes that your root group has been moved to this position. return [me.originCanvas[0]+me.canvasWidth*0.5,me.originCanvas[1]+me.canvasHeight*0.5]; }, getBorePos: func { # returns bore pos in canvas from center origin return [0,me.centerOffset]; }, getBorePosSlanted: func { # returns bore pos in canvas from center origin return [0,me.centerOffsetSlantedMeter*me.pixelPerMeterYSlant]; }, getPosFromCoord: func (gpsCoord, aircraft = nil) { # return pos in canvas from center origin if (aircraft== nil) { me.crft = geo.aircraft_position(); } else { me.crft = aircraft; } me.ptch = vector.Math.getPitch(me.crft,gpsCoord); me.dst = me.crft.direct_distance_to(gpsCoord); me.brng = me.crft.course_to(gpsCoord); me.hrz = math.cos(me.ptch*D2R)*me.dst; me.vel_gz = -math.sin(me.ptch*D2R)*me.dst; me.vel_gx = math.cos(me.brng*D2R) *me.hrz; me.vel_gy = math.sin(me.brng*D2R) *me.hrz; me.yaw = me.input.hdgTrue.getValue() * D2R; me.roll = me.input.roll.getValue() * D2R; me.pitch = me.input.pitch.getValue() * D2R; me.sy = math.sin(me.yaw); me.cy = math.cos(me.yaw); me.sr = math.sin(me.roll); me.cr = math.cos(me.roll); me.sp = math.sin(me.pitch); me.cp = math.cos(me.pitch); me.vel_bx = me.vel_gx * me.cy * me.cp + me.vel_gy * me.sy * me.cp + me.vel_gz * -me.sp; me.vel_by = me.vel_gx * (me.cy * me.sp * me.sr - me.sy * me.cr) + me.vel_gy * (me.sy * me.sp * me.sr + me.cy * me.cr) + me.vel_gz * me.cp * me.sr; me.vel_bz = me.vel_gx * (me.cy * me.sp * me.cr + me.sy * me.sr) + me.vel_gy * (me.sy * me.sp * me.cr - me.cy * me.sr) + me.vel_gz * me.cp * me.cr; me.dir_y = math.atan2(me.round0_(me.vel_bz), math.max(me.vel_bx, 0.001)) * R2D; me.dir_x = math.atan2(me.round0_(me.vel_by), math.max(me.vel_bx, 0.001)) * R2D; me.pos = me.getCenterPosFromDegs(me.dir_x,-me.dir_y); return [me.pos[0], me.pos[1], me.dir_x,-me.dir_y]; }, getPosFromDegs: func (yaw_deg, pitch_deg) { # return pos from bore if (yaw_deg > 89) yaw_deg = 89; if (yaw_deg < -89) yaw_deg = -89; if (pitch_deg < -89) pitch_deg = -89; if (pitch_deg > 89) pitch_deg = 89; var y = 0; var x = 0; if (me.slanted) { y = me.getVertDistSlanted(pitch_deg); x = me.pixelPerMeterX*((me.input.viewX.getValue() - me.hudX3d(y)) * math.tan(yaw_deg*D2R)); } else { y = -me.pixelPerMeterY*((me.input.viewX.getValue() - me.hud3dX) * math.tan(pitch_deg*D2R)); x = me.pixelPerMeterX*((me.input.viewX.getValue() - me.hud3dX) * math.tan(yaw_deg*D2R)); } return [x,y]; }, getCenterPosFromDegs: func (yaw_deg, pitch_deg) { # return pos from center origin if (yaw_deg > 89) yaw_deg = 89; if (yaw_deg < -89) yaw_deg = -89; if (pitch_deg < -89) pitch_deg = -89; if (pitch_deg > 89) pitch_deg = 89; if (me.slanted) { var y = me.getVertDistSlanted(pitch_deg); var x = me.pixelPerMeterX*((me.input.viewX.getValue() - me.hudX3d(y)) * math.tan(yaw_deg*D2R)); return [x,y+me.centerOffsetSlantedMeter*me.pixelPerMeterYSlant]; } else { var y = -me.pixelPerMeterY*((me.input.viewX.getValue() - me.hud3dX) * math.tan(pitch_deg*D2R)); var x = me.pixelPerMeterX*((me.input.viewX.getValue() - me.hud3dX) * math.tan(yaw_deg*D2R)); return [x,y+me.centerOffset]; } }, isCanvasPosClamped: func (x,y) { if (x>me.originCanvas[0]+me.canvasWidth or xme.originCanvas[1]+me.canvasHeight or y 0.01 ? x : 0; }, clamp: func(v, min, max) { return v < min ? min : v > max ? max : v; }, extrapolate: func (x, x1, x2, y1, y2) { return y1 + ((x - x1) / (x2 - x1)) * (y2 - y1); }, makeProperties_: func { me.input = { alpha: "orientation/alpha-deg", beta: "orientation/side-slip-deg", hdg: "orientation/heading-magnetic-deg", hdgTrue: "orientation/heading-deg", pitch: "orientation/pitch-deg", roll: "orientation/roll-deg", speed_d: "velocities/speed-down-fps", speed_e: "velocities/speed-east-fps", speed_n: "velocities/speed-north-fps", viewNumber: "sim/current-view/view-number", view0Z: "sim/view[0]/config/y-offset-m", view0X: "sim/view[0]/config/z-offset-m", viewZ: "sim/current-view/y-offset-m", viewX: "sim/current-view/z-offset-m", }; foreach(var name; keys(me.input)) { me.input[name] = props.globals.getNode(me.input[name], 1); } }, };