Skip to content

Commit

Permalink
fix: add custom field to FieldBoundaryDetector
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Vaiko committed Jan 12, 2025
1 parent befdbf1 commit 45f67b1
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 43 deletions.
2 changes: 1 addition & 1 deletion scripts/ai/jobs/CpAIJobBaleFinder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function CpAIJobBaleFinder:validateFieldPosition(isValid, errorMessage)
if tx == nil or tz == nil then
return false, g_i18n:getText("CP_error_not_on_field")
end
local fieldPolygon, _ = CpFieldUtil.getFieldPolygonAtWorldPosition(tx, tz)
local fieldPolygon, _ = CpFieldUtil.getFieldPolygonAtWorldPosition(tx, tz)
self:setFieldPolygon(fieldPolygon)
if fieldPolygon then
self.selectedFieldPlot:setWaypoints(fieldPolygon)
Expand Down
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
20 changes: 8 additions & 12 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,7 @@ 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)

print('CpAIJobFieldWork:applyCurrentState')
if isStartPositionInvalid then
local x, _, z = getWorldTranslation(vehicle.rootNode)
local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1)
Expand All @@ -92,12 +83,18 @@ 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)

print('CpAIJobFieldWork:validateFieldSetup')
if not isValid then
return isValid, errorMessage
end
Expand Down Expand Up @@ -204,7 +201,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

0 comments on commit 45f67b1

Please sign in to comment.