Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add custom field to FieldBoundaryDetector #353

Merged
merged 2 commits into from
Jan 12, 2025
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
6 changes: 3 additions & 3 deletions scripts/ai/jobs/CpAIJobCombineUnloader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ function CpAIJobCombineUnloader:applyCurrentState(vehicle, mission, farmId, isDi
x, _, z = getWorldTranslation(vehicle.rootNode)
self.cpJobParameters.fieldPosition:setPosition(x, z)
end
local x, z = self.cpJobParameters.fieldUnloadPosition:getPosition()
x, z = self.cpJobParameters.fieldUnloadPosition:getPosition()
local angle = self.cpJobParameters.fieldUnloadPosition:getAngle()
-- no field position from the previous job, use the vehicle's current position
if x == nil or z == nil or angle == nil then
x, _, z = getWorldTranslation(vehicle.rootNode)
local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1)
local angle = MathUtil.getYRotationFromDirection(dirX, dirZ)
angle = MathUtil.getYRotationFromDirection(dirX, dirZ)
self.cpJobParameters.fieldUnloadPosition:setPosition(x, z)
self.cpJobParameters.fieldUnloadPosition:setAngle(angle)
end
Expand Down Expand Up @@ -140,7 +140,7 @@ function CpAIJobCombineUnloader:validate(farmId)
------------------------------------
--- Validate selected field
-------------------------------------
local isValid, errorMessage = self:validateFieldPosition(isValid, errorMessage)
isValid, errorMessage = self:validateFieldPosition(isValid, errorMessage)

if not isValid then
return isValid, errorMessage
Expand Down
18 changes: 6 additions & 12 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,6 @@ end
---@param isStartPositionInvalid boolean resets the drive to target position by giants and the field position to the vehicle position.
function CpAIJobFieldWork:applyCurrentState(vehicle, mission, farmId, isDirectStart, isStartPositionInvalid)
CpAIJob.applyCurrentState(self, vehicle, mission, farmId, isDirectStart)

local _
local x, z = self.cpJobParameters.fieldPosition:getPosition()

if x == nil or z == nil then
x, _, z = getWorldTranslation(vehicle.rootNode)
end

self.cpJobParameters.fieldPosition:setPosition(x, z)

if isStartPositionInvalid then
local x, _, z = getWorldTranslation(vehicle.rootNode)
local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1)
Expand All @@ -92,12 +82,17 @@ function CpAIJobFieldWork:applyCurrentState(vehicle, mission, farmId, isDirectSt
self.cpJobParameters.startPosition:setAngle(angle)

self.cpJobParameters.fieldPosition:setPosition(x, z)
else
local x, z = self.cpJobParameters.fieldPosition:getPosition()
if x == nil or z == nil then
x, _, z = getWorldTranslation(vehicle.rootNode)
self.cpJobParameters.fieldPosition:setPosition(x, z)
end
end
end

--- Checks the field position setting.
function CpAIJobFieldWork:validateFieldSetup(isValid, errorMessage)

if not isValid then
return isValid, errorMessage
end
Expand Down Expand Up @@ -204,7 +199,6 @@ end
--- Button callback to generate a field work course.
function CpAIJobFieldWork:onClickGenerateFieldWorkCourse(callback)
local vehicle = self.vehicleParameter:getVehicle()
local fieldPolygon = self:getFieldPolygon()
local settings = vehicle:getCourseGeneratorSettings()
if self.isCustomField then
CpUtil.infoVehicle(vehicle, 'disabling island bypass on custom field')
Expand Down
1 change: 1 addition & 0 deletions scripts/field/CustomField.lua
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ function CustomField:getFieldNumber()
return s and tonumber(s)
end

---@return [{x, z}] vertices of the field polygon
function CustomField:getVertices()
return self.vertices
end
Expand Down
1 change: 1 addition & 0 deletions scripts/field/CustomFieldManager.lua
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ function CustomFieldManager:onClickRenameDialog(newName, clickOk, fieldToRename)
end
end

---@return CustomField|nil first custom field found at the given x and z coordinates.
function CustomFieldManager:getCustomField(x, z)
for _, field in pairs(self.fields) do
if field:isPointOnField(x, z) then
Expand Down
67 changes: 40 additions & 27 deletions scripts/field/FieldBoundaryDetector.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,57 @@
---@class FieldBoundaryDetector
FieldBoundaryDetector = CpObject()

function FieldBoundaryDetector:debug(...)
CpUtil.debugVehicle(CpDebug.DBG_COURSES, self.vehicle, ...)
end

function FieldBoundaryDetector:info(...)
CpUtil.infoVehicle(self.vehicle, ...)
end

--- Create a FieldBoundaryDetector instance and start the detection immediately. The detection is an asynchronous
--- process and FieldBoundaryDetector:update() must be called until it returns false to get the result.
---
--- If the user prefers custom fields, the detection will first check if there is a custom field at the given position,
--- and if so, use that as the field boundary. If not, the Giants field boundary detection is used.
--- If the Giants detection fails, the custom field is used as a fallback if exists at the position.
---
--- The result is available through getFieldPolygon(), which is a polygon representing the field boundary around
--- x, z.
--- x, z, and through getIslandPolygons() which is an array of polygons representing the islands inside the field.
---
---@param x number world X coordinate to start the detection at
---@param z number world Z coordinate to start the detection at
---@param vehicle table vehicle, this is used to generate the field course settings the Giants detection needs.
function FieldBoundaryDetector:init(x, z, vehicle)
self.logger = Logger('FieldBoundaryDetector', Logger.level.debug, CpDebug.DBG_COURSES)
self.vehicle = vehicle
self.updates = 0
self:info( 'Detecting field boundary at %.1f %.1f using the Giants function', x, z)
local customField = g_customFieldManager:getCustomField(x, z)
if customField and g_Courseplay.globalSettings.preferCustomFields:getValue() then
self.logger:info( 'Found custom field %s at %.1f %.1f and custom fields are preferred',
customField:getName(), x, z)
self:_useCustomField(customField)
return
end
self.logger:info( 'Detecting field boundary at %.1f %.1f using the Giants function', x, z)
local fieldCourseSettings, implementData = FieldCourseSettings.generate(vehicle)
self.courseField = FieldCourseField.generateAtPosition(x, z, fieldCourseSettings, function(courseField, success)
if success then
self:info('Field boundary detection successful, %d boundary points and %d islands',
#courseField.fieldRootBoundary.boundaryLine, #courseField.islands)
self.logger:info('Field boundary detection successful after %d updates, %d boundary points and %d islands',
self.updates, #courseField.fieldRootBoundary.boundaryLine, #courseField.islands)
self.fieldPolygon = self:_getAsVertices(courseField.fieldRootBoundary.boundaryLine)
self.islandPolygons = {}
for i, island in ipairs(courseField.islands) do
local islandBoundary = self:_getAsVertices(island.rootBoundary.boundaryLine)
table.insert(self.islandPolygons, islandBoundary)
end
else
self:info('Field boundary detection failed')
if customField then
self.logger:info('Field boundary detection failed after %d updates, but found custom field %s at %.1f %.1f',
self.updates, customField:getName(), x, z)
self:_useCustomField(customField)
return
end
end
self.success, self.result = success, courseField
end)
end

---@return boolean true if still in progress, false when done
function FieldBoundaryDetector:update(dt)
if self.courseField:update(dt, 0.00025) then
-- when we use the custom field, we are done immediately
if not self.useCustomField and self.courseField:update(dt, 0.00025) then
self.updates = self.updates + 1
return true
else
Expand All @@ -46,30 +63,26 @@ end

---@return table|nil [{x, y, z}] field polygon with game vertices
function FieldBoundaryDetector:getFieldPolygon()
if self.success then
return self:getAsVertices(self.result.fieldRootBoundary.boundaryLine)
end
return self.fieldPolygon
end

---@return table|nil [[{x, y, z}]] array of island polygons with game vertices (x, y, z)
function FieldBoundaryDetector:getIslandPolygons()
local islandPolygons = {}
if self.success then
for i, island in ipairs(self.result.islands) do
local islandBoundary = self:getAsVertices(island.rootBoundary.boundaryLine)
table.insert(islandPolygons, islandBoundary)
end
end
return islandPolygons
return self.islandPolygons
end

---@param boundaryLine table [[x, z]] array of arrays as the Giants functions return the field boundary
---@return table [{x, z}] array of vertices as the course generator needs it
function FieldBoundaryDetector:getAsVertices(boundaryLine)
function FieldBoundaryDetector:_getAsVertices(boundaryLine)
local vertices = {}
for _, point in ipairs(boundaryLine) do
local x, z = point[1], point[2]
table.insert(vertices, { x = x, y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 1, z), z = z })
end
return vertices
end

function FieldBoundaryDetector:_useCustomField(customField)
self.fieldPolygon = customField:getVertices()
self.useCustomField = true
end
Loading