mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0355af13b | ||
|
|
112945b3f6 | ||
|
|
09c46595e5 | ||
|
|
003b0c875e | ||
|
|
ee2668ac65 | ||
|
|
f47bf4771f | ||
|
|
8551830517 | ||
|
|
8b78090a57 | ||
|
|
d2bdb2a791 | ||
|
|
f8f0114f06 | ||
|
|
8619d48c80 | ||
|
|
4171cc45f5 | ||
|
|
5bccd2c6c9 | ||
|
|
a60626468c | ||
|
|
f9cb5d5966 | ||
|
|
33509a49e0 | ||
|
|
eda359e20f | ||
|
|
46882ed192 | ||
|
|
bbd5766688 | ||
|
|
ccf2f60068 | ||
|
|
fa4abed028 |
@@ -1,5 +1,6 @@
|
||||
--- **AI** - (R2.2) - Manages the process of an automatic A2A defense system based on an EWR network targets and coordinating CAP and GCI.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- Features:
|
||||
--
|
||||
@@ -18,6 +19,19 @@
|
||||
-- * Quickly setup an A2A defense system using @{#AI_A2A_GCICAP}.
|
||||
-- * Setup a more advanced defense system using @{#AI_A2A_DISPATCHER}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- [AID-A2A - AI A2A Dispatching](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching/AID-A2A%20-%20AI%20A2A%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## YouTube Channel:
|
||||
--
|
||||
-- [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # QUICK START GUIDE
|
||||
--
|
||||
@@ -183,18 +197,6 @@ do -- AI_A2A_DISPATCHER
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ### [AI\_A2A\_DISPATCHER Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ### [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- It includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy air movements that are detected by a ground based radar network.
|
||||
@@ -796,10 +798,17 @@ do -- AI_A2A_DISPATCHER
|
||||
--
|
||||
-- Use the method @{#AI_A2A_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting.
|
||||
--
|
||||
-- ## 11. Airbase capture:
|
||||
--
|
||||
-- ## 11. Q & A:
|
||||
-- Different squadrons can be located at one airbase.
|
||||
-- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership.
|
||||
-- As a result, the GCI and CAP will stop!
|
||||
-- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes
|
||||
-- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players.
|
||||
--
|
||||
-- ### 11.1. Which countries will be selected for each coalition?
|
||||
-- ## 12. Q & A:
|
||||
--
|
||||
-- ### 12.1. Which countries will be selected for each coalition?
|
||||
--
|
||||
-- Which countries are assigned to a coalition influences which units are available to the coalition.
|
||||
-- For example because the mission calls for a EWR radar on the blue side the Ukraine might be chosen as a blue country
|
||||
@@ -807,7 +816,7 @@ do -- AI_A2A_DISPATCHER
|
||||
-- Some countries assign different tasking to aircraft, for example Germany assigns the CAP task to F-4E Phantoms but the USA does not.
|
||||
-- Therefore if F4s are wanted as a coalition's CAP or GCI aircraft Germany will need to be assigned to that coalition.
|
||||
--
|
||||
-- ### 11.2. Country, type, load out, skill and skins for CAP and GCI aircraft?
|
||||
-- ### 12.2. Country, type, load out, skill and skins for CAP and GCI aircraft?
|
||||
--
|
||||
-- * Note these can be from any countries within the coalition but must be an aircraft with one of the main tasks being "CAP".
|
||||
-- * Obviously skins which are selected must be available to all players that join the mission otherwise they will see a default skin.
|
||||
@@ -995,9 +1004,13 @@ do -- AI_A2A_DISPATCHER
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead )
|
||||
--self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead )
|
||||
|
||||
|
||||
self:HandleEvent( EVENTS.Land )
|
||||
self:HandleEvent( EVENTS.EngineShutdown )
|
||||
|
||||
-- Handle the situation where the airbases are captured.
|
||||
self:HandleEvent( EVENTS.BaseCaptured )
|
||||
|
||||
self:SetTacticalDisplay( false )
|
||||
|
||||
self:__Start( 5 )
|
||||
@@ -1005,6 +1018,25 @@ do -- AI_A2A_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_A2A_DISPATCHER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_A2A_DISPATCHER:OnEventBaseCaptured( EventData )
|
||||
|
||||
local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured.
|
||||
|
||||
self:I( "Captured " .. AirbaseName )
|
||||
|
||||
-- Now search for all squadrons located at the airbase, and sanatize them.
|
||||
for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do
|
||||
if Squadron.AirbaseName == AirbaseName then
|
||||
Squadron.Resources = -999 -- The base has been captured, and the resources are eliminated. No more spawning.
|
||||
Squadron.Captured = true
|
||||
self:I( "Squadron " .. SquadronName .. " captured." )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_DISPATCHER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_A2A_DISPATCHER:OnEventCrashOrDead( EventData )
|
||||
@@ -1480,6 +1512,7 @@ do -- AI_A2A_DISPATCHER
|
||||
|
||||
DefenderSquadron.Name = SquadronName
|
||||
DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName )
|
||||
DefenderSquadron.AirbaseName = DefenderSquadron.Airbase:GetName()
|
||||
if not DefenderSquadron.Airbase then
|
||||
error( "Cannot find airbase with name:" .. AirbaseName )
|
||||
end
|
||||
@@ -1497,6 +1530,7 @@ do -- AI_A2A_DISPATCHER
|
||||
end
|
||||
DefenderSquadron.Resources = Resources
|
||||
DefenderSquadron.TemplatePrefixes = TemplatePrefixes
|
||||
DefenderSquadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured.
|
||||
|
||||
self:F( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, Resources } } )
|
||||
|
||||
@@ -1663,16 +1697,19 @@ do -- AI_A2A_DISPATCHER
|
||||
|
||||
local DefenderSquadron = self:GetSquadron( SquadronName )
|
||||
|
||||
if ( not DefenderSquadron.Resources ) or ( DefenderSquadron.Resources and DefenderSquadron.Resources > 0 ) then
|
||||
|
||||
local Cap = DefenderSquadron.Cap
|
||||
if Cap then
|
||||
local CapCount = self:CountCapAirborne( SquadronName )
|
||||
self:F( { CapCount = CapCount } )
|
||||
if CapCount < Cap.CapLimit then
|
||||
local Probability = math.random()
|
||||
if Probability <= Cap.Probability then
|
||||
return DefenderSquadron
|
||||
if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured.
|
||||
|
||||
if ( not DefenderSquadron.Resources ) or ( DefenderSquadron.Resources and DefenderSquadron.Resources > 0 ) then -- And, if there are sufficient resources.
|
||||
|
||||
local Cap = DefenderSquadron.Cap
|
||||
if Cap then
|
||||
local CapCount = self:CountCapAirborne( SquadronName )
|
||||
self:F( { CapCount = CapCount } )
|
||||
if CapCount < Cap.CapLimit then
|
||||
local Probability = math.random()
|
||||
if Probability <= Cap.Probability then
|
||||
return DefenderSquadron
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1693,10 +1730,13 @@ do -- AI_A2A_DISPATCHER
|
||||
|
||||
local DefenderSquadron = self:GetSquadron( SquadronName )
|
||||
|
||||
if ( not DefenderSquadron.Resources ) or ( DefenderSquadron.Resources and DefenderSquadron.Resources > 0 ) then
|
||||
local Gci = DefenderSquadron.Gci
|
||||
if Gci then
|
||||
return DefenderSquadron
|
||||
if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured.
|
||||
|
||||
if ( not DefenderSquadron.Resources ) or ( DefenderSquadron.Resources and DefenderSquadron.Resources > 0 ) then -- And, if there are sufficient resources.
|
||||
local Gci = DefenderSquadron.Gci
|
||||
if Gci then
|
||||
return DefenderSquadron
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
|
||||
@@ -767,6 +767,24 @@ do -- COORDINATE
|
||||
-- Return wind direction and strength km/h.
|
||||
return direction, strength
|
||||
end
|
||||
|
||||
--- Returns the wind direction (from) and strength.
|
||||
-- @param #COORDINATE self
|
||||
-- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground.
|
||||
-- @return Direction the wind is blowing from in degrees.
|
||||
function COORDINATE:GetWindWithTurbulenceVec3(height)
|
||||
|
||||
-- AGL height if
|
||||
local landheight=self:GetLandHeight()+0.1 -- we at 0.1 meters to be sure to be above ground since wind is zero below ground level.
|
||||
|
||||
-- Point at which the wind is evaluated.
|
||||
local point={x=self.x, y=math.max(height or self.y, landheight), z=self.z}
|
||||
|
||||
-- Get wind velocity vector including turbulences.
|
||||
local vec3 = atmosphere.getWindWithTurbulence(point)
|
||||
|
||||
return vec3
|
||||
end
|
||||
|
||||
|
||||
--- Returns a text documenting the wind direction (from) and strength according the measurement system @{Settings}.
|
||||
|
||||
@@ -1908,7 +1908,7 @@ do -- SET_UNIT
|
||||
if _DATABASE then
|
||||
self:_FilterStart()
|
||||
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
|
||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrashOr )
|
||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
|
||||
end
|
||||
|
||||
@@ -687,9 +687,9 @@ function COMMANDCENTER:ReportMissionsPlayers( ReportGroup )
|
||||
|
||||
Report:Add( "Players active in all missions." )
|
||||
|
||||
for MissionID, Mission in pairs( self.Missions ) do
|
||||
local Mission = Mission -- Tasking.Mission#MISSION
|
||||
Report:Add( " - " .. Mission:ReportPlayers() )
|
||||
for MissionID, MissionData in pairs( self.Missions ) do
|
||||
local Mission = MissionData -- Tasking.Mission#MISSION
|
||||
Report:Add( " - " .. Mission:ReportPlayersPerTask(ReportGroup) )
|
||||
end
|
||||
|
||||
self:MessageToGroup( Report:Text(), ReportGroup )
|
||||
|
||||
@@ -657,5 +657,26 @@ function UTILS.Randomize(value, fac, lower, upper)
|
||||
return r
|
||||
end
|
||||
|
||||
--- Calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of two vectors. The result is a number.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
|
||||
-- @return #number Scalar product of the two vectors a*b.
|
||||
function UTILS.VecDot(a, b)
|
||||
return a.x*b.x + a.y*b.y + a.z*b.z
|
||||
end
|
||||
|
||||
--- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 3D vector.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @return #number Norm of the vector.
|
||||
function UTILS.VecNorm(a)
|
||||
return math.sqrt(UTILS.VecDot(a, a))
|
||||
end
|
||||
|
||||
--- Calculate the [cross product](https://en.wikipedia.org/wiki/Cross_product) of two 3D vectors. The result is a 3D vector.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
|
||||
-- @return DCS#Vec3 Vector
|
||||
function UTILS.VecCross(a, b)
|
||||
return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x}
|
||||
end
|
||||
|
||||
|
||||
@@ -334,7 +334,7 @@ end
|
||||
--- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase.
|
||||
-- @param #AIRBASE self
|
||||
-- @param #string AirbaseName The Airbase Name.
|
||||
-- @return Wrapper.Airbase#AIRBASE self
|
||||
-- @return #AIRBASE self
|
||||
function AIRBASE:FindByName( AirbaseName )
|
||||
|
||||
local AirbaseFound = _DATABASE:FindAirbase( AirbaseName )
|
||||
|
||||
@@ -121,6 +121,80 @@ function POSITIONABLE:Destroy( GenerateEvent )
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns a pos3 table of the objects current position and orientation in 3D space. X, Y, Z values are unit vectors defining the objects orientation.
|
||||
-- Coordinates are dependent on the position of the maps origin.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Position Table consisting of the point and orientation tables.
|
||||
function POSITIONABLE:GetPosition()
|
||||
self:F2( self.PositionableName )
|
||||
|
||||
local DCSPositionable = self:GetDCSObject()
|
||||
|
||||
if DCSPositionable then
|
||||
local PositionablePosition = DCSPositionable:getPosition()
|
||||
self:T3( PositionablePosition )
|
||||
return PositionablePosition
|
||||
end
|
||||
|
||||
BASE:E( { "Cannot GetPositionVec3", Positionable = self, Alive = self:IsAlive() } )
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns a {@DCS#Vec3} table of the objects current orientation in 3D space. X, Y, Z values are unit vectors defining the objects orientation.
|
||||
-- X is the orientation parallel to the movement of the object, Z perpendicular and Y vertical orientation.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Vec3 X orientation, i.e. parallel to the direction of movement.
|
||||
-- @return DCS#Vec3 Y orientation, i.e. vertical.
|
||||
-- @return DCS#Vec3 Z orientation, i.e. perpendicular to the direction of movement.
|
||||
function POSITIONABLE:GetOrientation()
|
||||
local position=self:GetPosition()
|
||||
if position then
|
||||
return position.x, position.y, position.z
|
||||
else
|
||||
BASE:E( { "Cannot GetOrientation", Positionable = self, Alive = self:IsAlive() } )
|
||||
return nil, nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns a {@DCS#Vec3} table of the objects current X orientation in 3D space, i.e. along the direction of movement.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Vec3 X orientation, i.e. parallel to the direction of movement.
|
||||
function POSITIONABLE:GetOrientationX()
|
||||
local position=self:GetPosition()
|
||||
if position then
|
||||
return position.x
|
||||
else
|
||||
BASE:E( { "Cannot GetOrientationX", Positionable = self, Alive = self:IsAlive() } )
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns a {@DCS#Vec3} table of the objects current Y orientation in 3D space, i.e. vertical orientation.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Vec3 Y orientation, i.e. vertical.
|
||||
function POSITIONABLE:GetOrientationY()
|
||||
local position=self:GetPosition()
|
||||
if position then
|
||||
return position.y
|
||||
else
|
||||
BASE:E( { "Cannot GetOrientationY", Positionable = self, Alive = self:IsAlive() } )
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns a {@DCS#Vec3} table of the objects current Z orientation in 3D space, i.e. perpendicular to direction of movement.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Vec3 Z orientation, i.e. perpendicular to movement.
|
||||
function POSITIONABLE:GetOrientationZ()
|
||||
local position=self:GetPosition()
|
||||
if position then
|
||||
return position.z
|
||||
else
|
||||
BASE:E( { "Cannot GetOrientationZ", Positionable = self, Alive = self:IsAlive() } )
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Position The 3D position vectors of the POSITIONABLE.
|
||||
@@ -582,6 +656,153 @@ function POSITIONABLE:GetVelocityMPS()
|
||||
return 0
|
||||
end
|
||||
|
||||
--- Returns the Angle of Attack of a positionable.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return #number Angle of attack in degrees.
|
||||
function POSITIONABLE:GetAoA()
|
||||
|
||||
-- Get position of the unit.
|
||||
local unitpos = self:GetPosition()
|
||||
|
||||
if unitpos then
|
||||
|
||||
-- Get velocity vector of the unit.
|
||||
local unitvel = self:GetVelocityVec3()
|
||||
|
||||
if unitvel and UTILS.VecNorm(unitvel)~=0 then
|
||||
|
||||
-- Get wind vector including turbulences.
|
||||
local wind=self:GetCoordinate():GetWindWithTurbulenceVec3()
|
||||
|
||||
-- Include wind vector.
|
||||
unitvel.x=unitvel.x-wind.x
|
||||
unitvel.y=unitvel.y-wind.y
|
||||
unitvel.z=unitvel.z-wind.z
|
||||
|
||||
-- Unit velocity transformed into aircraft axes directions.
|
||||
local AxialVel = {}
|
||||
|
||||
-- Transform velocity components in direction of aircraft axes.
|
||||
AxialVel.x = UTILS.VecDot(unitpos.x, unitvel)
|
||||
AxialVel.y = UTILS.VecDot(unitpos.y, unitvel)
|
||||
AxialVel.z = UTILS.VecDot(unitpos.z, unitvel)
|
||||
|
||||
-- AoA is angle between unitpos.x and the x and y velocities.
|
||||
local AoA = math.acos(UTILS.VecDot({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/UTILS.VecNorm({x = AxialVel.x, y = AxialVel.y, z = 0}))
|
||||
|
||||
--Set correct direction:
|
||||
if AxialVel.y > 0 then
|
||||
AoA = -AoA
|
||||
end
|
||||
|
||||
-- Return AoA value in degrees.
|
||||
return math.deg(AoA)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the unit's climb or descent angle.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return #number Climb or descent angle in degrees.
|
||||
function POSITIONABLE:GetClimbAnge()
|
||||
|
||||
-- Get position of the unit.
|
||||
local unitpos = self:GetPosition()
|
||||
|
||||
if unitpos then
|
||||
|
||||
-- Get velocity vector of the unit.
|
||||
local unitvel = self:GetVelocityVec3()
|
||||
|
||||
if unitvel and UTILS.VecNorm(unitvel)~=0 then
|
||||
|
||||
return math.asin(unitvel.y/UTILS.VecNorm(unitvel))
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the pitch angle of a unit.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return #number Pitch ange in degrees.
|
||||
function POSITIONABLE:GetPitch()
|
||||
|
||||
-- Get position of the unit.
|
||||
local unitpos = self:GetPosition()
|
||||
|
||||
if unitpos then
|
||||
return math.deg(math.asin(unitpos.x.y))
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the roll angle of a unit.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return #number Pitch ange in degrees.
|
||||
function POSITIONABLE:GetRoll()
|
||||
|
||||
-- Get position of the unit.
|
||||
local unitpos = self:GetPosition()
|
||||
|
||||
if unitpos then
|
||||
|
||||
--first, make a vector that is perpendicular to y and unitpos.x with cross product
|
||||
local cp = UTILS.VecCross(unitpos.x, {x = 0, y = 1, z = 0})
|
||||
|
||||
--now, get dot product of of this cross product with unitpos.z
|
||||
local dp = UTILS.VecDot(cp, unitpos.z)
|
||||
|
||||
--now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|)
|
||||
local Roll = math.acos(dp/(UTILS.VecNorm(cp)*UTILS.VecNorm(unitpos.z)))
|
||||
|
||||
--now, have to get sign of roll.
|
||||
-- by convention, making right roll positive
|
||||
-- to get sign of roll, use the y component of unitpos.z. For right roll, y component is negative.
|
||||
|
||||
if unitpos.z.y > 0 then -- left roll, flip the sign of the roll
|
||||
Roll = -Roll
|
||||
end
|
||||
|
||||
return math.deg(Roll)
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the yaw angle of a unit.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return #number Yaw ange in degrees.
|
||||
function POSITIONABLE:GetYaw()
|
||||
|
||||
local unitpos = self:GetPosition()
|
||||
if unitpos then
|
||||
-- get unit velocity
|
||||
local unitvel = self:GetVelocityVec3()
|
||||
|
||||
if unitvel and UTILS.VecNorm(unitvel) ~= 0 then --must have non-zero velocity!
|
||||
local AxialVel = {} --unit velocity transformed into aircraft axes directions
|
||||
|
||||
--transform velocity components in direction of aircraft axes.
|
||||
AxialVel.x = UTILS.VecDot(unitpos.x, unitvel)
|
||||
AxialVel.y = UTILS.VecDot(unitpos.y, unitvel)
|
||||
AxialVel.z = UTILS.VecDot(unitpos.z, unitvel)
|
||||
|
||||
--Yaw is the angle between unitpos.x and the x and z velocities
|
||||
--define right yaw as positive
|
||||
local Yaw = math.acos(UTILS.VecDot({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/UTILS.VecNorm({x = AxialVel.x, y = 0, z = AxialVel.z}))
|
||||
|
||||
--now set correct direction:
|
||||
if AxialVel.z > 0 then
|
||||
Yaw = -Yaw
|
||||
end
|
||||
return Yaw
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Returns the message text with the callsign embedded (if there is one).
|
||||
-- @param #POSITIONABLE self
|
||||
|
||||
Reference in New Issue
Block a user