From 912c162eee594c021e7b183721c8afce90c8e27e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 27 Sep 2023 15:41:52 +0200 Subject: [PATCH 1/3] Logic fixes for GetRandomCoordinateWithoutBuildings() --- Moose Development/Moose/Core/Zone.lua | 89 +++++++++++++++++++-------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index f38fe83c4..2d2ccaecb 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -53,7 +53,8 @@ -- @module Core.Zone -- @image Core_Zones.JPG ---- @type ZONE_BASE +--- +-- @type ZONE_BASE -- @field #string ZoneName Name of the zone. -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -- @field #number DrawID Unique ID of the drawn zone on the F10 map. @@ -195,7 +196,7 @@ end --- Returns if a PointVec2 is within the zone. (Name is misleading, actually takes a #COORDINATE) -- @param #ZONE_BASE self --- @param Core.Point#COORDINATE PointVec2 The coordinate to test. +-- @param Core.Point#COORDINATE Coordinate The coordinate to test. -- @return #boolean true if the PointVec2 is within the zone. function ZONE_BASE:IsPointVec2InZone( Coordinate ) local InZone = self:IsVec2InZone( Coordinate:GetVec2() ) @@ -953,7 +954,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local Include = false if not UnitCategories then - -- Anythink found is included. + -- Anything found is included. Include = true else -- Check if found object is in specified categories. @@ -984,9 +985,9 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) if ObjectCategory == Object.Category.SCENERY then local SceneryType = ZoneObject:getTypeName() local SceneryName = ZoneObject:getName() - --BASE:I("SceneryType "..SceneryType.."SceneryName"..SceneryName) + --BASE:I("SceneryType "..SceneryType.." SceneryName "..tostring(SceneryName)) self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} - self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) + self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( tostring(SceneryName), ZoneObject) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName] ) self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end @@ -1442,8 +1443,11 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma local T1 = timer.getTime() local buildings = {} + local buildingzones = {} + if self.ScanData and self.ScanData.BuildingCoordinates then buildings = self.ScanData.BuildingCoordinates + buildingzones = self.ScanData.BuildingZones else -- build table of buildings coordinates for _,_object in pairs (objects) do @@ -1455,28 +1459,32 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma MARKER:New(scenery:GetCoordinate(),"Building"):ToAll() end buildings[#buildings+1] = scenery:GetCoordinate() + local bradius = scenery:GetBoundingRadius() or dist + local bzone = ZONE_RADIUS:New("Building-"..math.random(1,100000),scenery:GetVec2(),bradius,false) + buildingzones[#buildingzones+1] = bzone + --bzone:DrawZone(-1,{1,0,0},Alpha,FillColor,FillAlpha,1,ReadOnly) end end end self.ScanData.BuildingCoordinates = buildings + self.ScanData.BuildingZones = buildingzones end -- max 1000 tries local rcoord = nil - local found = false + local found = true local iterations = 0 for i=1,1000 do iterations = iterations + 1 rcoord = self:GetRandomCoordinate(inner,outer) - found = false - for _,_coord in pairs (buildings) do - local coord = _coord -- Core.Point#COORDINATE + found = true + for _,_coord in pairs (buildingzones) do + local zone = _coord -- Core.Zone#ZONE_RADIUS -- keep >50m dist from buildings - if coord:Get3DDistance(rcoord) > dist then - found = true - else + if zone:IsPointVec2InZone(rcoord) then found = false + break end end if found then @@ -1488,15 +1496,43 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma end end + if not found then + -- max 1000 tries + local rcoord = nil + local found = true + local iterations = 0 + + for i=1,1000 do + iterations = iterations + 1 + rcoord = self:GetRandomCoordinate(inner,outer) + found = true + for _,_coord in pairs (buildings) do + local coord = _coord -- Core.Point#COORDINATE + -- keep >50m dist from buildings + if coord:Get3DDistance(rcoord) < dist then + found = false + end + end + if found then + -- we have a winner! + if markfinal then + MARKER:New(rcoord,"FREE"):ToAll() + end + break + end + end + end + T1=timer.getTime() - self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %d",tostring(found),iterations,T1-T0)) + self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0)) if found then return rcoord else return nil end end ---- @type ZONE +--- +-- @type ZONE -- @extends #ZONE_RADIUS @@ -1580,8 +1616,8 @@ function ZONE:FindByName( ZoneName ) end - ---- @type ZONE_UNIT +--- +-- @type ZONE_UNIT -- @field Wrapper.Unit#UNIT ZoneUNIT -- @extends Core.Zone#ZONE_RADIUS @@ -1616,7 +1652,11 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) if (Offset.dx or Offset.dy) and (Offset.rho or Offset.theta) then error("Cannot use (dx, dy) with (rho, theta)") end + end + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius, true ) ) + + if Offset then self.dy = Offset.dy or 0.0 self.dx = Offset.dx or 0.0 self.rho = Offset.rho or 0.0 @@ -1624,8 +1664,6 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) self.relative_to_unit = Offset.relative_to_unit or false end - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius, true ) ) - self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT @@ -1721,7 +1759,8 @@ function ZONE_UNIT:GetVec3( Height ) return Vec3 end ---- @type ZONE_GROUP +--- +-- @type ZONE_GROUP -- @extends #ZONE_RADIUS @@ -1807,7 +1846,8 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) end ---- @type ZONE_POLYGON_BASE +--- +-- @type ZONE_POLYGON_BASE -- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. -- @extends #ZONE_BASE @@ -2456,7 +2496,8 @@ function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, C return self end ---- @type ZONE_POLYGON +--- +-- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE @@ -2584,7 +2625,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) local minmarkcoord = COORDINATE:NewFromVec3(minVec3) local maxmarkcoord = COORDINATE:NewFromVec3(maxVec3) local ZoneRadius = minmarkcoord:Get2DDistance(maxmarkcoord)/2 - +-- self:I("Scan Radius:" ..ZoneRadius) local CenterVec3 = self:GetCoordinate():GetVec3() --[[ this a bit shaky in functionality it seems @@ -2907,7 +2948,7 @@ end do -- ZONE_ELASTIC - --- @type ZONE_ELASTIC + -- @type ZONE_ELASTIC -- @field #table points Points in 2D. -- @field #table setGroups Set of GROUPs. -- @field #table setOpsGroups Set of OPSGROUPS. @@ -3107,7 +3148,7 @@ end do -- ZONE_AIRBASE - --- @type ZONE_AIRBASE + -- @type ZONE_AIRBASE -- @field #boolean isShip If `true`, airbase is a ship. -- @field #boolean isHelipad If `true`, airbase is a helipad. -- @field #boolean isAirdrome If `true`, airbase is an airdrome. From 55fb8f20643f4bd96f194728220d953367600d22 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 27 Sep 2023 18:08:02 +0200 Subject: [PATCH 2/3] nil check added --- Moose Development/Moose/Core/Zone.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 2d2ccaecb..a6c975b79 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -190,6 +190,7 @@ end -- @param Core.Point#COORDINATE Coordinate The coordinate to test. -- @return #boolean true if the coordinate is within the zone. function ZONE_BASE:IsCoordinateInZone( Coordinate ) + if not Coordinate then return false end local InZone = self:IsVec2InZone( Coordinate:GetVec2() ) return InZone end From 04a7c912ea0984794c31d0fc3b89073ad5880454 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 27 Sep 2023 22:21:17 +0200 Subject: [PATCH 3/3] Update Airboss.lua - Improved into wind --- Moose Development/Moose/Ops/Airboss.lua | 138 ++++++++++++++++++++---- 1 file changed, 119 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index ef510bcd2..a1616b965 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -4075,7 +4075,7 @@ function AIRBOSS:_CheckRecoveryTimes() -- Check that wind is blowing from a direction > 5° different from the current heading. local hdg = self:GetHeading() - local wind = self:GetHeadingIntoWind() + local wind = self:GetHeadingIntoWind(nextwindow.SPEED) local delta = self:_GetDeltaHeading( hdg, wind ) local uturn = delta > 5 @@ -6728,7 +6728,7 @@ function AIRBOSS:_AddMarshalGroup( flight, stack ) -- If the carrier is supposed to turn into the wind, we take the wind coordinate. if self.recoverywindow and self.recoverywindow.WIND then - brc = self:GetBRCintoWind() + brc = self:GetBRCintoWind(self.recoverywindow.SPEED) end -- Get charlie time estimate. @@ -11553,7 +11553,7 @@ end -- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned. -- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position. -- @return #number Carrier heading in degrees. -function AIRBOSS:GetHeadingIntoWind( magnetic, coord ) +function AIRBOSS:GetHeadingIntoWind_old( magnetic, coord ) local function adjustDegreesForWindSpeed(windSpeed) local degreesAdjustment = 0 @@ -11613,13 +11613,108 @@ function AIRBOSS:GetHeadingIntoWind( magnetic, coord ) return intowind end +--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway. +-- Implementation based on [Mags & Bambi](https://magwo.github.io/carrier-cruise/). +-- @param #AIRBOSS self +-- @param #number vdeck Desired wind velocity over deck in knots. +-- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned. +-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position. +-- @return #number Carrier heading in degrees. +-- @return #number Carrier speed in knots to reach desired wind speed on deck. +function AIRBOSS:GetHeadingIntoWind( vdeck, magnetic, coord ) + + -- Default offset angle. + local Offset=self.carrierparam.rwyangle or 0 + + -- Get direction the wind is blowing from. + local windfrom, vwind=self:GetWind(18, nil ,coord) + + -- Ships min/max speed. + local Vmin=4 + local Vmax=UTILS.KmphToKnots(self.carrier:GetSpeedMax()) + + -- No wind. will stay on current heading. + if vwind<0.1 then + local h=self:GetHeading(magnetic) + return h, math.min(vdeck, Vmax) + end + + -- Convert wind speed to knots. + vwind=UTILS.MpsToKnots(vwind) + + -- Wind to in knots. + local windto=(windfrom+180)%360 + + -- Offset angle in rad. We also define the rotation to be clock-wise, which requires a minus sign. + local alpha=math.rad(-Offset) + + -- Constant. + local C = math.sqrt(math.cos(alpha)^2 / math.sin(alpha)^2 + 1) + + + -- Upper limit of desired speed due to max boat speed. + local vdeckMax=vwind + math.cos(alpha) * Vmax + + -- Lower limit of desired speed due to min boat speed. + local vdeckMin=vwind + math.cos(alpha) * Vmin + + + -- Speed of ship so it matches the desired speed. + local v=0 + + -- Angle wrt. to wind TO-direction + local theta=0 + + if vdeck>vdeckMax then + -- Boat cannot go fast enough + + -- Set max speed. + v=Vmax + + -- Calculate theta. + theta = math.asin(v/(vwind*C)) - math.asin(-1/C) + + elseif vdeckvwind then + -- Too little wind + + -- Set theta to 90° + theta=math.pi/2 + + -- Set speed. + v = math.sqrt(vdeck^2 - vwind^2) + + else + -- Normal case + theta = math.asin(vdeck * math.sin(alpha) / vwind) + v = vdeck * math.cos(alpha) - vwind * math.cos(theta) + end + + -- Magnetic heading. + local magvar= magnetic and self.magvar or 0 + + -- Ship heading so cross wind is min for the given wind. + local intowind = (540 + (windto - magvar + math.deg(theta) )) % 360 + + return intowind, v +end + --- Get base recovery course (BRC) when the carrier would head into the wind. -- This includes the current wind direction and accounts for the angled runway. -- @param #AIRBOSS self +-- @param #number vdeck Desired wind velocity over deck in knots. -- @return #number BRC into the wind in degrees. -function AIRBOSS:GetBRCintoWind() +function AIRBOSS:GetBRCintoWind(vdeck) -- BRC is the magnetic heading. - return self:GetHeadingIntoWind( true ) + return self:GetHeadingIntoWind(vdeck, true ) end --- Get final bearing (FB) of carrier. @@ -13525,28 +13620,32 @@ function AIRBOSS:CarrierTurnIntoWind( time, vdeck, uturn ) -- Wind speed. local _, vwind = self:GetWind() + -- Desired wind on deck in knots. + local vdeck=UTILS.MpsToKnots(vdeck) + + -- Get heading into the wind accounting for angled runway. + local hiw, speedknots = self:GetHeadingIntoWind(vdeck) + -- Speed of carrier in m/s but at least 4 knots. - local vtot = math.max( vdeck - vwind, UTILS.KnotsToMps( 4 ) ) + local vtot = UTILS.KnotsToMps(speedknots) -- Distance to travel local dist = vtot * time - -- Speed in knots - local speedknots = UTILS.MpsToKnots( vtot ) + -- Distance in NM. local distNM = UTILS.MetersToNM( dist ) - -- Debug output - self:I( self.lid .. string.format( "Carrier steaming into the wind (%.1f kts). Distance=%.1f NM, Speed=%.1f knots, Time=%d sec.", UTILS.MpsToKnots( vwind ), distNM, speedknots, time ) ) - - -- Get heading into the wind accounting for angled runway. - local hiw = self:GetHeadingIntoWind() - -- Current heading. local hdg = self:GetHeading() -- Heading difference. local deltaH = self:_GetDeltaHeading( hdg, hiw ) + -- Debug output + self:I( self.lid .. string.format( "Carrier steaming into the wind (%.1f kts). Heading=%03d-->%03d (Delta=%.1f), Speed=%.1f knots, Distance=%.1f NM, Time=%d sec", + UTILS.MpsToKnots( vwind ), hdg, hiw, deltaH, speedknots, distNM, speedknots, time ) ) + + -- Current coordinate. local Cv = self:GetCoordinate() local Ctiw = nil -- Core.Point#COORDINATE @@ -13560,7 +13659,7 @@ function AIRBOSS:CarrierTurnIntoWind( time, vdeck, uturn ) Csoo = Cv:Translate( 750, hdg ):Translate( 750, hiw ) -- Heading into wind from Csoo. - local hsw = self:GetHeadingIntoWind( false, Csoo ) + local hsw = self:GetHeadingIntoWind(vdeck, false, Csoo ) -- Into the wind coord. Ctiw = Csoo:Translate( dist, hsw ) @@ -13572,7 +13671,7 @@ function AIRBOSS:CarrierTurnIntoWind( time, vdeck, uturn ) Csoo = Cv:Translate( 900, hdg ):Translate( 900, hiw ) -- Heading into wind from Csoo. - local hsw = self:GetHeadingIntoWind( false, Csoo ) + local hsw = self:GetHeadingIntoWind(vdeck, false, Csoo ) -- Into the wind coord. Ctiw = Csoo:Translate( dist, hsw ) @@ -13584,7 +13683,7 @@ function AIRBOSS:CarrierTurnIntoWind( time, vdeck, uturn ) Csoo = Cv:Translate( 1100, hdg - 90 ):Translate( 1000, hiw ) -- Heading into wind from Csoo. - local hsw = self:GetHeadingIntoWind( false, Csoo ) + local hsw = self:GetHeadingIntoWind(vdeck, false, Csoo ) -- Into the wind coord. Ctiw = Csoo:Translate( dist, hsw ) @@ -13596,7 +13695,7 @@ function AIRBOSS:CarrierTurnIntoWind( time, vdeck, uturn ) Csoo = Cv:Translate( 1200, hdg - 90 ):Translate( 1000, hiw ) -- Heading into wind from Csoo. - local hsw = self:GetHeadingIntoWind( false, Csoo ) + local hsw = self:GetHeadingIntoWind(vdeck, false, Csoo ) -- Into the wind coord. Ctiw = Csoo:Translate( dist, hsw ) @@ -13809,7 +13908,8 @@ function AIRBOSS:_CheckCarrierTurning() local hdg if self.turnintowind then -- We are now steaming into the wind. - hdg = self:GetHeadingIntoWind( false ) + local vdeck=self.recoverywindow and self.recoverywindow.SPEED or 20 + hdg = self:GetHeadingIntoWind(vdeck, false) else -- We turn towards the next waypoint. hdg = self:GetCoordinate():HeadingTo( self:_GetNextWaypoint() )