Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion scripts/courseGenerator/Field.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ CourseGenerator.Field = Field

---@param id string unique ID for this field for logging
---@param num number field number as shown in game
---@param boundary Polygon|nil boundary of the field
---@param boundary [{x, y}]|nil boundary of the field
function Field:init(id, num, boundary)
self.id = id
self.num = num
Expand All @@ -17,6 +17,7 @@ function Field:init(id, num, boundary)
self.islands = {}
if boundary then
self.boundary:calculateProperties()
self:_smoothBoundary()
end
end

Expand Down Expand Up @@ -66,10 +67,18 @@ function Field.loadSavedFields(fileName)
for _, f in pairs(fields) do
f:getBoundary():calculateProperties()
f:setupIslands()
f:_smoothBoundary()
end
return fields
end

--- Smooth out zigzags in the boundary. Zigzags are created when the FieldScanner is running at high resolution
--- bigger than the game field pixel size
function Field:_smoothBoundary()
CourseGenerator.SplineHelper.smooth(self:getBoundary(), 2, 1, #self:getBoundary(), math.rad(5), math.rad(45))
self:getBoundary():ensureMinimumEdgeLength(CourseGenerator.cMaxEdgeLength, math.rad(30))
end

--- Center of the field (centroid)
---@return Vector
function Field:getCenter()
Expand Down
7 changes: 5 additions & 2 deletions scripts/courseGenerator/FieldworkCourseMultiVehicle.lua
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,11 @@ function FieldworkCourseMultiVehicle:_offsetConnectingPath(path, ix, offsetVecto
section:append(path[i])
i = i + 1
until i > #path or not path[i]:getAttributes():isOnConnectingPath()
local offsetConnectingPath = _generateOffsetSection(section, offsetVector)
_appendOffsetSection(section, offsetConnectingPath, offsetPath)
if #section > 1 then
-- connecting paths with a single vertex can be ignored
local offsetConnectingPath = _generateOffsetSection(section, offsetVector)
_appendOffsetSection(section, offsetConnectingPath, offsetPath)
end
return i
end

Expand Down
33 changes: 21 additions & 12 deletions scripts/courseGenerator/SplineHelper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ local SplineHelper = {}
---@param from number start index
---@param to number end index (may be less than from, to wrap around a polygon's end
---@param s number tuck factor
local function tuck(p, from, to, s)
local function tuck(p, from, to, s, minAngle, maxAngle)
for _, cv, pv, nv in p:vertices(from, to) do
if pv and cv and nv then
if not cv.isCorner and cv.dA and math.abs(cv.dA) > CourseGenerator.cMinSmoothingAngle then
if not cv.isCorner and cv.dA and math.abs(cv.dA) > (minAngle or CourseGenerator.cMinSmoothingAngle)
and math.abs(cv.dA) < (maxAngle or math.huge) then
local m = (pv + nv) / 2
local cm = m - cv
cv.x, cv.y = cv.x + s * cm.x, cv.y + s * cm.y
Expand All @@ -19,7 +20,7 @@ local function tuck(p, from, to, s)
end

--- Add a vertex between existing ones
local function refine(p, from, to)
local function refine(p, from, to, minAngle, maxAngle)
-- iterate through the existing table but do not insert the
-- new points, only remember the index where they would end up
-- (as we do not want to modify the table while iterating)
Expand All @@ -28,33 +29,41 @@ local function refine(p, from, to)
for i, cv, _, nv in p:vertices(from, to) do
-- initialize ix to the first value of i
if nv and cv then
if not cv.isCorner and cv.dA and math.abs(cv.dA) > CourseGenerator.cMinSmoothingAngle then
if not cv.isCorner and cv.dA and math.abs(cv.dA) > (minAngle or CourseGenerator.cMinSmoothingAngle)
and math.abs(cv.dA) < (maxAngle or math.huge) then
local m = (nv + cv) / 2
local newVertex = cv:clone()
newVertex.x, newVertex.y = m.x, m.y
ix = ix + 1
table.insert(verticesToInsert, {ix = ix, vertex = newVertex})
table.insert(verticesToInsert, { ix = ix, vertex = newVertex })
end
end
ix = ix + 1
end
for _, v in ipairs(verticesToInsert) do
table.insert(p, v.ix, v.vertex )
table.insert(p, v.ix, v.vertex)
end
p:calculateProperties(from, to + #verticesToInsert)
end

---@return number the index where
function SplineHelper.smooth(p, order, from, to)
---@param p Polyline
---@param order number how many times smooth should be called, more calls will result in a smoother curve
---@param from number start index
---@param to number end index (may be less than from, to wrap around a polygon's end
---@param minAngle number|nil ignore vertices where the delta angle is less than this, default is CourseGenerator.cMinSmoothingAngle
---@param maxAngle number|nil ignore vertices where the delta angle is greater than this, default is math.huge
---@return number the index where the smoothing ended, that is, the new value of 'to' after smoothing inserted
--- new vertices
function SplineHelper.smooth(p, order, from, to, minAngle, maxAngle)
if (order <= 0) then
return
else
local origSize = #p
refine(p, from, to)
refine(p, from, to, minAngle, maxAngle)
to = to + #p - origSize
tuck(p, from, to, 0.5)
tuck(p, from, to, -0.15)
SplineHelper.smooth(p, order - 1, from, to)
tuck(p, from, to, 0.5, minAngle, maxAngle)
tuck(p, from, to, -0.15, minAngle, maxAngle)
SplineHelper.smooth(p, order - 1, from, to, minAngle, maxAngle)
end
return to
end
Expand Down
14 changes: 7 additions & 7 deletions scripts/courseGenerator/test/FieldTest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ lu.EPS = 0.01
function testField()
local fields = CourseGenerator.Field.loadSavedFields('fields/Coldborough.xml')
lu.assertEquals(#fields, 9)
lu.assertEquals(#fields[8].boundary, 135)
lu.assertEquals(#fields[8].boundary, 90)
local field = fields[8]
local center = field:getCenter()
lu.assertAlmostEquals(center.x, 380.8, 0.1)
lu.assertAlmostEquals(center.y, 31.14, 0.1)
lu.assertAlmostEquals(center.x, 381.41, 0.1)
lu.assertAlmostEquals(center.y, 31.3, 0.1)
local x1, y1, x2, y2 = field:getBoundingBox()
lu.assertAlmostEquals(x1, 307.15)
lu.assertAlmostEquals(y1, -80.84)
lu.assertAlmostEquals(x2, 452.84)
lu.assertAlmostEquals(y2, 157.33)
lu.assertAlmostEquals(x1, 307.18)
lu.assertAlmostEquals(y1, -80.66)
lu.assertAlmostEquals(x2, 452.82)
lu.assertAlmostEquals(y2, 157.16)
end
os.exit(lu.LuaUnit.run())
6 changes: 3 additions & 3 deletions scripts/geometry/Polygon.lua
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,9 @@ function Polygon:getLongestEdgeDirection()
return self.longestEdgeDirection
end

function Polygon:ensureMinimumEdgeLength(minimumLength)
Polyline.ensureMinimumEdgeLength(self, minimumLength)
if (self[1] - self[#self]):length() < minimumLength then
function Polygon:ensureMinimumEdgeLength(minimumLength, maxDeltaAngle)
Polyline.ensureMinimumEdgeLength(self, minimumLength, maxDeltaAngle)
if (self[1] - self[#self]):length() < minimumLength and self:_canRemoveVertex(#self, maxDeltaAngle) then
table.remove(self, #self)
end
self:calculateProperties(#self - 1)
Expand Down
19 changes: 16 additions & 3 deletions scripts/geometry/Polyline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ function Polyline:removeGlitches()
local i = 1
while i < #self do
local dA = self:at(i).dA
if dA and dA > math.pi - 0.2 then
if dA and math.abs(dA) > math.pi - 0.2 then
table.remove(self, i)
else
i = i + 1
Expand All @@ -329,11 +329,24 @@ function Polyline:removeGlitches()
self:calculateProperties()
end

function Polyline:_canRemoveVertex(i, maxDeltaAngle)
if not maxDeltaAngle then
return true
else
-- only remove vertices which aren't around a corner
return math.abs(self:at(i + 1).dA) < maxDeltaAngle and math.abs(self:at(i).dA) < maxDeltaAngle
end
end


--- If two vertices are closer than minimumLength, replace them with one between.
function Polyline:ensureMinimumEdgeLength(minimumLength)
---@param minimumLength number After this operation, no two vertices will be closer than minimumLength
---@param maxDeltaAngle number|nil when specified, vertices where the delta angle is bigger than this are not removed,
--- thus, corners are preserved
function Polyline:ensureMinimumEdgeLength(minimumLength, maxDeltaAngle)
local i = 1
while i < #self do
if (self:at(i + 1) - self:at(i)):length() < minimumLength then
if (self:at(i + 1) - self:at(i)):length() < minimumLength and self:_canRemoveVertex(i, maxDeltaAngle) then
table.remove(self, i + 1)
else
i = i + 1
Expand Down