Fix/custom drawings (#18)
Publish Release / build (push) Successful in 13s

Reviewed-on: #18
Co-authored-by: dutchie031 <timrorije@gmail.com>
This commit was merged in pull request #18.
This commit is contained in:
2026-08-17 09:32:01 +00:00
committed by dutchie031
parent 52f97f7c92
commit 8c75ba5027
11 changed files with 205 additions and 65 deletions
+7 -14
View File
@@ -8,22 +8,15 @@
### Bug Fixes ### Bug Fixes
- Issue #11 - Issue #11
PR #17
Supply crate spawning now checks for free space and will not spawn if the area is too crowded. Supply crate spawning now checks for free space and will not spawn if the area is too crowded.
Additionally different units will spawn in different areas depending on loading side. Additionally different units will spawn in different areas depending on loading side.
- PR #15 PR #17
Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone. - Fixed command wiring for supply hubs for better and more accurate detection of units spawning and entering/exiting zone.
PR #15
- Fixed custom drawings not being drawn correctly.
## [0.12.1] 2026-07 PR #18
- Fixed CAP Callbacks not working since the change to a transpiled script. Now a global callback circumvents this issue.
### Breaking Changes PR #18
### New Features
### Bug Fixes
- Fixed stage drawing to only be checked when a stage completed. Now done on stage number changed.
## [0.12.0] 2026-06 ## [0.12.0] 2026-06
+5 -5
View File
@@ -220,7 +220,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")"
} }
} }
} }
@@ -260,7 +260,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
}, },
stopCondition = { stopCondition = {
duration = durationBefore10, duration = durationBefore10,
condition = "return Spearhead.DcsUtil.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)", condition = "return GlobalCapCallBacks.NeedsRTBInTen(\"" .. groupName .. "\", 0.10)",
} }
} }
}, },
@@ -273,7 +273,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishRTBInTen, \"" .. groupName .. "\")"
} }
} }
} }
@@ -299,7 +299,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
}, },
stopCondition = { stopCondition = {
duration = durationAfter10, duration = durationAfter10,
condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")", condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)",
} }
} }
}, },
@@ -312,7 +312,7 @@ function CAP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
@@ -0,0 +1,26 @@
local DcsUtil = require("classes.util.DcsUtil")
local Events = require("classes.spearhead_events")
GlobalCapCallBacks = {}
function GlobalCapCallBacks.IsBingoFuel(groupName, fuelPercent)
return DcsUtil.IsBingoFuel(groupName, fuelPercent)
end
function GlobalCapCallBacks.NeedsRTBInTen(groupName, fuelOffset)
return DcsUtil.NeedsRTBInTen(groupName, fuelOffset)
end
function GlobalCapCallBacks.PublishRTBInTen(groupName)
return Events.PublishRTBInTen(groupName)
end
function GlobalCapCallBacks.PublishRTB(groupName)
return Events.PublishRTB(groupName)
end
function GlobalCapCallBacks.PublishOnStation(groupName)
return Events.PublishOnStation(groupName)
end
@@ -247,7 +247,7 @@ function INTERCEPT.getInterceptTaskPoint(groupName, currentPoint, targetPoint, a
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
@@ -379,7 +379,7 @@ function INTERCEPT.getUnitInterceptTaskPoint(groupName, currentPoint, targetPosi
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
+2 -2
View File
@@ -189,7 +189,7 @@ function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")"
} }
} }
} }
@@ -270,7 +270,7 @@ function SWEEP.getAsTasking(groupName, airbase, capZone, capConfig)
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
+1 -1
View File
@@ -231,7 +231,7 @@ function Database.New(Logger)
for key, layer_object in pairs(layer.objects) do for key, layer_object in pairs(layer.objects) do
if Util.startswith(layer_object.name, "drawing_", true) then if Util.startswith(layer_object.name, "drawing_", true) then
local object = layer_object --[[@as DrawingObject]] local object = layer_object --[[@as DrawingObject]]
local customDrawing = CustomDrawing.New(object) local customDrawing = CustomDrawing.New(object, nil, self._logger.LogLevel)
table.insert(self._tables.CustomDrawings, customDrawing) table.insert(self._tables.CustomDrawings, customDrawing)
end end
end end
+6 -6
View File
@@ -92,7 +92,7 @@ do --setup route util
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishOnStation, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishOnStation, \"" .. groupName .. "\")"
} }
} }
} }
@@ -122,7 +122,7 @@ do --setup route util
}, },
stopCondition = { stopCondition = {
duration = durationBefore10, duration = durationBefore10,
condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\", 0.10)", condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)",
} }
} }
}, },
@@ -135,7 +135,7 @@ do --setup route util
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishRTBInTen, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishRTBInTen, \"" .. groupName .. "\")"
} }
} }
} }
@@ -156,7 +156,7 @@ do --setup route util
}, },
stopCondition = { stopCondition = {
duration = durationAfter10, duration = durationAfter10,
condition = "return Spearhead.DcsUtil.IsBingoFuel(\"" .. groupName .. "\")", condition = "return GlobalCapCallBacks.IsBingoFuel(\"" .. groupName .. "\", 0.10)",
} }
} }
}, },
@@ -169,7 +169,7 @@ do --setup route util
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishRTB, \"" .. groupName .. "\")" command = "pcall(GlobalCapCallBacks.PublishRTB, \"" .. groupName .. "\")"
} }
} }
} }
@@ -375,7 +375,7 @@ do --setup route util
action = { action = {
id = "Script", id = "Script",
params = { params = {
command = "pcall(Spearhead.Events.PublishRTB, \"" .. command = "pcall(GlobalCapCallBacks.PublishRTB, \"" ..
groupName .. "\")" groupName .. "\")"
} }
} }
@@ -216,12 +216,15 @@ end
---@private ---@private
function GlobalStageManager:UpdateDrawings(stageNumber) function GlobalStageManager:UpdateDrawings(stageNumber)
self.logger:debug("Updating custom drawings for stage number: " .. tostring(stageNumber))
local drawings = self.database:getCustomDrawings() local drawings = self.database:getCustomDrawings()
for _, drawing in pairs(drawings) do for _, drawing in pairs(drawings) do
local startStage, stopStage = drawing:GetStartAndStop() local startStage, stopStage = drawing:GetStartAndStop()
if stageNumber >= startStage and stageNumber < stopStage then if stageNumber >= startStage and stageNumber < stopStage then
self.logger:debug("Drawing " .. drawing:GetName() .. " is active for stage number: " .. tostring(stageNumber))
drawing:Draw() drawing:Draw()
else else
self.logger:debug("Drawing " .. drawing:GetName() .. " is not active for stage number: " .. tostring(stageNumber))
drawing:Remove() drawing:Remove()
end end
end end
@@ -1,23 +1,27 @@
local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper") local DrawingHelper = require("classes.stageClasses.drawings.helper.DrawingHelper")
local Util = require("classes.util.Util") local Util = require("classes.util.Util")
local Logger = require("classes.util.Logger")
---@class CustomDrawing ---@class CustomDrawing
---@field private _id integer? ---@field private _id integer?
---@field private _drawingObject DrawingObject ---@field private _drawingObject DrawingObject
---@field private _startingStage number ---@field private _startingStage number
---@field private _removeAtStage number ---@field private _removeAtStage number
---@field private _logger Logger
local CustomDrawing = {} local CustomDrawing = {}
CustomDrawing.__index = CustomDrawing CustomDrawing.__index = CustomDrawing
---@param drawingObject DrawingObject ---@param drawingObject DrawingObject
---@param id integer? ---@param id integer?
---@param loglevel LogLevel
---@return CustomDrawing ---@return CustomDrawing
function CustomDrawing.New(drawingObject, id) function CustomDrawing.New(drawingObject, id, loglevel)
local self = setmetatable({}, CustomDrawing) local self = setmetatable({}, CustomDrawing)
self._drawingObject = drawingObject self._drawingObject = drawingObject
self._id = id self._id = id
self._logger = Logger.new("CustomDrawing", loglevel)
local name = drawingObject.name local name = drawingObject.name
local split = Util.split_string(name or "", "_") local split = Util.split_string(name or "", "_")
@@ -28,6 +32,11 @@ function CustomDrawing.New(drawingObject, id)
return self return self
end end
---@return string
function CustomDrawing:GetName()
return self._drawingObject.name
end
---@return number start ---@return number start
---@return number stop ---@return number stop
function CustomDrawing:GetStartAndStop() function CustomDrawing:GetStartAndStop()
@@ -35,11 +44,18 @@ function CustomDrawing:GetStartAndStop()
end end
function CustomDrawing:Draw() function CustomDrawing:Draw()
self._id = DrawingHelper.Draw(self._drawingObject) self._logger:debug("Drawing custom drawing with ID " .. tostring(self._id))
if self._id ~= nil then
DrawingHelper.Remove(self._id)
end
self._id = DrawingHelper.Draw(self._drawingObject, self._logger)
end end
function CustomDrawing:Remove() function CustomDrawing:Remove()
if self._id ~= nil then if self._id ~= nil then
self._logger:debug("Removing custom drawing with ID " .. tostring(self._id))
DrawingHelper.Remove(self._id) DrawingHelper.Remove(self._id)
self._id = nil self._id = nil
end end
@@ -1,25 +1,39 @@
local Util = require("classes.util.Util")
---@class DrawingHelper ---@class DrawingHelper
local DrawingHelper = {} local DrawingHelper = {}
DrawingHelper.__index = DrawingHelper DrawingHelper.__index = DrawingHelper
local customDrawingIdIncrementer = 4210 local customDrawingIdIncrementer = 4210
---@param object DrawingObject ---@param object DrawingObject
---@param logger Logger?
---@return integer? id ---@return integer? id
function DrawingHelper.Draw(object) function DrawingHelper.Draw(object, logger)
if object == nil then if object == nil then
if logger then
logger:warn("DrawingHelper.Draw called with nil object")
end
return nil return nil
end end
local id = DrawingHelper.GetAndAddId() local id = DrawingHelper.GetAndAddId()
if logger then
logger:debug("Drawing object with ID " .. tostring(id) .. " and primitive type: " .. tostring(object.primitiveType))
end
if(object.primitiveType == "Polygon") then if(object.primitiveType == "Polygon") then
DrawingHelper.DrawPolygon(object--[[@as Polygon]], id) DrawingHelper.DrawPolygon(object--[[@as Polygon]], id, logger)
elseif(object.primitiveType == "Line") then elseif(object.primitiveType == "Line") then
DrawingHelper.DrawLine(object--[[@as Line]], id) DrawingHelper.DrawLine(object--[[@as Line]], id, logger)
elseif(object.primitiveType == "TextBox") then elseif(object.primitiveType == "TextBox") then
DrawingHelper.DrawTextBox(object--[[@as TextBox]], id) DrawingHelper.DrawTextBox(object--[[@as TextBox]], id)
else
if logger then
logger:warn("Unknown primitive type: " .. tostring(object.primitiveType))
end
end end
return id return id
@@ -36,16 +50,25 @@ end
---@param points Array<Vec3> ---@param points Array<Vec3>
---@param fillColor table ---@param fillColor table
---@param lineColor table ---@param lineColor table
local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle) ---@param lineStyle LineType
---@param lineThickness number
---@param logger Logger?
local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineStyle, lineThickness, logger)
if lineThickness == nil or lineThickness <= 0 then
lineStyle = 0
end
local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. "," local functionString = "trigger.action.markupToAll(" .. shapeID .. ", -1, " .. drawID .. ","
for _, point in ipairs(points) do
for _, point in pairs(points) do
functionString = functionString .. " { x=" .. point.x .. ", y=0,z=" .. point.z .. "}," functionString = functionString .. " { x=" .. point.x .. ", y=0,z=" .. point.z .. "},"
end end
functionString = functionString .. functionString = functionString .. "{0,1,0,1}, {0,1,0,1}, " .. lineStyle .. ")"
"{ " .. lineColor[1] .. "," .. lineColor[2] .. "," .. lineColor[3] .. "," .. lineColor[4] .. "}, " ..
"{ " .. fillColor[1] .. "," .. fillColor[2] .. "," .. fillColor[3] .. "," .. fillColor[4] .. "}, " .. if logger then
lineStyle .. ")" logger:debug("Drawing complex drawing with ID " .. tostring(drawID) .. " and function string: " .. functionString)
end
---@diagnostic disable-next-line: deprecated ---@diagnostic disable-next-line: deprecated
local f, err = loadstring(functionString) local f, err = loadstring(functionString)
@@ -55,12 +78,21 @@ local function MarkupToAll(shapeID, drawID, points, fillColor, lineColor, lineS
env.error("Something failed when drawing complex drawing" .. err) env.error("Something failed when drawing complex drawing" .. err)
end end
if logger then
logger:debug("Drawing with fill color: " .. table.concat(fillColor, ",") .. " and line color: " .. table.concat(lineColor, ",") .. " and line style: " .. tostring(lineStyle) .. " and line thickness: " .. tostring(lineThickness))
end
trigger.action.setMarkupColorFill(drawID, fillColor)
trigger.action.setMarkupColor(drawID, lineColor)
trigger.action.setMarkupTypeLine(drawID, lineStyle)
end end
---@private ---@private
---@param object Polygon ---@param object Polygon
---@param id integer ---@param id integer
function DrawingHelper.DrawPolygon(object, id) ---@param logger Logger?
function DrawingHelper.DrawPolygon(object, id, logger)
if object == nil then if object == nil then
return return
end end
@@ -75,11 +107,12 @@ function DrawingHelper.DrawPolygon(object, id)
end end
---@param oval Oval ---@param oval Oval
local function DrawOval(oval) ---@param logger Logger?
local function DrawOval(oval, logger)
---@type Array<Vec3> ---@type Array<Vec3>
local points = {} local points = {}
local pointsNo = 30 local pointsNo = 30
local angleStep = (2 * math.pi) / points local angleStep = (2 * math.pi) / pointsNo
local fillColor = DrawingHelper.ColorToColorTable(oval.fillColorString) local fillColor = DrawingHelper.ColorToColorTable(oval.fillColorString)
local color = DrawingHelper.ColorToColorTable(oval.colorString) local color = DrawingHelper.ColorToColorTable(oval.colorString)
@@ -91,20 +124,37 @@ function DrawingHelper.DrawPolygon(object, id)
local y = oval.mapY + (oval.r2 * math.sin(angle)) local y = oval.mapY + (oval.r2 * math.sin(angle))
table.insert(points, { x = x, y = 0, z = y } ) table.insert(points, { x = x, y = 0, z = y } )
end end
MarkupToAll(7, id, points, fillColor, color, lineStyle) MarkupToAll(7, id, points, fillColor, color, lineStyle, oval.thickness, logger)
end end
---@param free Free ---@param free Free
local function DrawFree(free) ---@param logger Logger?
local function DrawFree(free, logger)
local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString) local fillColor = DrawingHelper.ColorToColorTable(free.fillColorString)
local color = DrawingHelper.ColorToColorTable(free.colorString) local color = DrawingHelper.ColorToColorTable(free.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(free.style) local lineStyle = DrawingHelper.ToLineStyleInteger(free.style)
local points = {} local keys = {}
for _, point in ipairs(free.points) do for k, _ in pairs(free.points) do
table.insert(points, { x = point.x, y = 0, z = point.y } ) table.insert(keys, k)
end end
MarkupToAll(7, id, points, fillColor, color, lineStyle)
table.sort(keys, function(a, b) return a < b end)
local points = {}
for _, k in ipairs(keys) do
local point = free.points[k]
local newPoint = { x = free.mapX + point.x, y = 0, z = free.mapY + point.y }
local firstPoint = points[1]
if firstPoint == nil or newPoint.x ~= firstPoint.x or newPoint.z ~= firstPoint.z then
logger:debug("Drawing free polygon point " .. tostring(k) .. ": " .. tostring(free.points[k].x) .. ", " .. tostring(free.points[k].y))
table.insert(points, newPoint)
end
end
MarkupToAll(7, id, points, fillColor, color, lineStyle, free.thickness, logger)
end end
---@param rect Rect ---@param rect Rect
@@ -116,38 +166,49 @@ function DrawingHelper.DrawPolygon(object, id)
local pointA = { x = rect.mapX, y = 0, z = rect.mapY } local pointA = { x = rect.mapX, y = 0, z = rect.mapY }
local pointB = { x = rect.mapX + rect.width, y = 0, z = rect.mapY + rect.height } local pointB = { x = rect.mapX + rect.width, y = 0, z = rect.mapY + rect.height }
trigger.action.rectToAll(-1, id, pointA, pointB, color, fillColor, lineStyle, true) trigger.action.rectToAll(-1, id, pointA, pointB, color, fillColor, lineStyle, true)
end end
---@param arrow Arrow ---@param arrow Arrow
local function DrawArrow(arrow) ---@param logger Logger?
local function DrawArrow(arrow, logger)
local fillColor = DrawingHelper.ColorToColorTable(arrow.fillColorString) local fillColor = DrawingHelper.ColorToColorTable(arrow.fillColorString)
local color = DrawingHelper.ColorToColorTable(arrow.colorString) local color = DrawingHelper.ColorToColorTable(arrow.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(arrow.style) local lineStyle = DrawingHelper.ToLineStyleInteger(arrow.style)
local startPoint = { x = arrow.mapX, y = 0, z = arrow.mapY } if logger then
logger:debug("Drawing arrow with start point: " .. tostring(arrow.mapX) .. ", " .. tostring(arrow.mapY) .. " and angle: " .. tostring(arrow.angle) .. " and length: " .. tostring(arrow.length))
end
local endPoint = { x = arrow.mapX, y = 0, z = arrow.mapY }
local rad = math.rad(arrow.angle or 0) local rad = math.rad(arrow.angle or 0)
local length = arrow.length or 100 local length = arrow.length or 100
local endPoint = { x = arrow.mapX + length * math.cos(rad), y = 0, z = arrow.mapY + length * math.sin(rad) } local startPoint = { x = arrow.mapX - length * math.sin(rad), y = 0, z = arrow.mapY + length * math.cos(rad) }
trigger.action.arrowToAll(-1, id, startPoint, endPoint, color, fillColor, lineStyle, true) trigger.action.arrowToAll(-1, id, startPoint, endPoint, color, fillColor, lineStyle, true)
end end
if logger then
logger:debug("Drawing polygon with ID " .. tostring(id) .. " and polygon mode: " .. tostring(object.polygonMode))
end
if object.polygonMode == "circle" then if object.polygonMode == "circle" then
DrawCircle(object--[[@as Circle]]) DrawCircle(object--[[@as Circle]])
elseif object.polygonMode == "oval" then elseif object.polygonMode == "oval" then
DrawOval(object--[[@as Oval]]) DrawOval(object--[[@as Oval]], logger)
elseif object.polygonMode == "free" then elseif object.polygonMode == "free" then
DrawFree(object--[[@as Free]]) DrawFree(object--[[@as Free]], logger)
elseif object.polygonMode == "rect" then elseif object.polygonMode == "rect" then
DrawRect(object--[[@as Rect]]) DrawRect(object--[[@as Rect]])
elseif object.polygonMode == "arrow" then elseif object.polygonMode == "arrow" then
DrawArrow(object--[[@as Arrow]]) DrawArrow(object--[[@as Arrow]], logger)
end end
end end
---@private ---@private
---@param object Line ---@param object Line
---@param id integer ---@param id integer
function DrawingHelper.DrawLine(object, id) ---@param logger Logger?
function DrawingHelper.DrawLine(object, id, logger)
---@type Array<Vec3> ---@type Array<Vec3>
local points = {} local points = {}
@@ -158,7 +219,7 @@ function DrawingHelper.DrawLine(object, id)
local color = DrawingHelper.ColorToColorTable(object.colorString) local color = DrawingHelper.ColorToColorTable(object.colorString)
local lineStyle = DrawingHelper.ToLineStyleInteger(object.style) local lineStyle = DrawingHelper.ToLineStyleInteger(object.style)
MarkupToAll(1, id, points, color, color, lineStyle) MarkupToAll(1, id, points, color, color, lineStyle, object.thickness, logger)
end end
---@private ---@private
@@ -177,16 +238,14 @@ function DrawingHelper.Remove(id)
trigger.action.removeMark(id) trigger.action.removeMark(id)
end end
---@param hexStr string ---@param hexStr string
---@return table ---@return table
function DrawingHelper.ColorToColorTable(hexStr) function DrawingHelper.ColorToColorTable(hexStr)
hexStr = hexStr:gsub("0x", "") hexStr = hexStr:gsub("0x", "")
local a = tonumber(hexStr:sub(1, 2), 16) / 255 local r = tonumber(hexStr:sub(1, 2), 16) / 255
local r = tonumber(hexStr:sub(3, 4), 16) / 255 local g = tonumber(hexStr:sub(3, 4), 16) / 255
local g = tonumber(hexStr:sub(5, 6), 16) / 255 local b = tonumber(hexStr:sub(5, 6), 16) / 255
local b = tonumber(hexStr:sub(7, 8), 16) / 255 local a = tonumber(hexStr:sub(7, 8), 16) / 255
return { r, g , b , a } return { r, g , b , a }
end end
+43
View File
@@ -297,6 +297,49 @@ do -- INIT UTIL
return false return false
end end
---@param points Array<Vec3>
---@return Array<Vec3>
function UTIL.getConvexHull3d(points)
if #points == 0 then
return {}
end
---comment
---@param a Vec3
---@param b Vec3
---@param c Vec3
---@return boolean
local function ccw(a, b, c)
return (b.z - a.z) * (c.x - a.x) > (b.x - a.x) * (c.z - a.z)
end
table.sort(points, function(left, right)
return left.z < right.z
end)
local hull = {}
-- lower hull
for _, point in pairs(points) do
while #hull >= 2 and not ccw(hull[#hull - 1], hull[#hull], point) do
table.remove(hull, #hull)
end
table.insert(hull, point)
end
-- upper hull
local t = #hull + 1
for i = #points, 1, -1 do
local point = points[i]
while #hull >= t and not ccw(hull[#hull - 1], hull[#hull], point) do
table.remove(hull, #hull)
end
table.insert(hull, point)
end
table.remove(hull, #hull)
return hull
end
---comment ---comment
---@param points Array<Vec2> points ---@param points Array<Vec2> points
---@return Array<Vec2> hullPoints ---@return Array<Vec2> hullPoints