diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index e5f54b667..3639f5ab8 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -249,7 +249,23 @@ CALLSIGN={ Stetson = 22, Wrath = 23, }, - + Intruder = { + Raygun = 4, + Heartless = 5, + Viceroy = 6, + Cupcake = 7, + ["Flying Tiger"] = 8, + ["Flying Ace"] = 9, + Buckeye = 10, + Goldplate = 11, + Phoenix = 12, + Electron = 13, + Rustler = 14, + Vixen = 15, + Jackal = 16, + Milestone = 17, + Devil = 18, + }, } --#CALLSIGN --- Utilities static class. @@ -2014,6 +2030,12 @@ function UTILS.GetCallsignName(Callsign) end end + for name, value in pairs(CALLSIGN.Intruder) do + if value==Callsign then + return name + end + end + return "Ghostrider" end @@ -4747,7 +4769,7 @@ function UTILS.DoStringIn(State,DoString) end --- Show a picture on the screen to all --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4770,7 +4792,7 @@ end --- Show a picture on the screen to Coalition -- @param #number Coalition Coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4795,7 +4817,7 @@ end --- Show a picture on the screen to Country -- @param #number Country Country ID, can be country.id.USA, country.id.RUSSIA, etc. --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4818,7 +4840,7 @@ end --- Show a picture on the screen to Group -- @param Wrapper.Group#GROUP Group Group to show the picture to --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4841,7 +4863,7 @@ end --- Show a picture on the screen to Unit -- @param Wrapper.Unit#UNIT Unit Unit to show the picture to --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -5290,3 +5312,147 @@ function UTILS.CreateAirbaseEnum() _savefile(string.format("%s-enums.txt", env.mission.theatre), text) --env.info(text) end + +--- Calculate then center and radius of a circle enclosing a list if DCS#Vec2 points. +-- @param #table points Table of DCS#Vec2 entries +-- @return DCS#Vec2 center DCS#Vec2 +-- @return #number radius +function UTILS.GetCenterAndRadius(points) + if #points == 0 then + return nil, nil + end + + -- Calculate centroid (average of all points) + local sumX, sumY = 0, 0 + for _, p in ipairs(points) do + sumX = sumX + p.x + sumY = sumY + p.y + end + + local center = { + x = sumX / #points, + y = sumY / #points + } + + -- Find maximum distance from center to any point + local maxDist = 0 + for _, p in ipairs(points) do + local dx = p.x - center.x + local dy = p.y - center.y + local dist = math.sqrt(dx*dx + dy*dy) + if dist > maxDist then + maxDist = dist + end + end + + return center, maxDist +end + +--- More accurate: Minimum bounding circle (Welzl's algorithm), calculate then center and radius of a circle enclosing a list if DCS#Vec2 points. +-- @param #table points Table of DCS#Vec2 entries +-- @return DCS#Vec2 center DCS#Vec2 +-- @return #number radius +function UTILS.GetMinimumBoundingCircle(points) + if #points == 0 then + return nil, nil + end + + -- Calculate distance between two points + local function distance(p1, p2) + local dx = p2.x - p1.x + local dy = p2.y - p1.y + return math.sqrt(dx*dx + dy*dy) + end + + -- Circle from 2 points (diameter) + local function circleFrom2Points(p1, p2) + local center = { + x = (p1.x + p2.x) / 2, + y = (p1.y + p2.y) / 2 + } + local radius = distance(p1, p2) / 2 + return center, radius + end + + -- Circle from 3 points (circumcircle) + local function circleFrom3Points(p1, p2, p3) + local ax, ay = p1.x, p1.y + local bx, by = p2.x, p2.y + local cx, cy = p3.x, p3.y + + local d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) + + if math.abs(d) < 0.0001 then + -- Points are collinear, use 2-point circle + return circleFrom2Points(p1, p3) + end + + local aSq = ax*ax + ay*ay + local bSq = bx*bx + by*by + local cSq = cx*cx + cy*cy + + local ux = (aSq * (by - cy) + bSq * (cy - ay) + cSq * (ay - by)) / d + local uy = (aSq * (cx - bx) + bSq * (ax - cx) + cSq * (bx - ax)) / d + + local center = {x = ux, y = uy} + local radius = distance(center, p1) + + return center, radius + end + + -- Check if point is inside circle + local function isInside(center, radius, point, tolerance) + tolerance = tolerance or 0.0001 + return distance(center, point) <= radius + tolerance + end + + -- Welzl's algorithm (recursive) + local function welzlHelper(pts, n, boundary) + -- Base cases + if n == 0 or #boundary == 3 then + if #boundary == 0 then + return {x = 0, y = 0}, 0 + elseif #boundary == 1 then + return {x = boundary[1].x, y = boundary[1].y}, 0 + elseif #boundary == 2 then + return circleFrom2Points(boundary[1], boundary[2]) + else + return circleFrom3Points(boundary[1], boundary[2], boundary[3]) + end + end + + -- Pick a random point + local p = pts[n] + + -- Get circle without this point + local center, radius = welzlHelper(pts, n - 1, boundary) + + -- If point is inside, we're done + if isInside(center, radius, p) then + return center, radius + end + + -- Otherwise, point must be on the boundary + local newBoundary = {} + for i = 1, #boundary do + newBoundary[i] = boundary[i] + end + table.insert(newBoundary, p) + + return welzlHelper(pts, n - 1, newBoundary) + end + + -- Shuffle points for better average performance + local pts = {} + for i, p in ipairs(points) do + pts[i] = {x = p.x, y = p.y} + end + + -- Simple shuffle + for i = #pts, 2, -1 do + local j = math.random(1, i) + pts[i], pts[j] = pts[j], pts[i] + end + + return welzlHelper(pts, #pts, {}) +end