Skip to content

Commit

Permalink
Merge pull request #397 from Courseplay/fieldpolygon
Browse files Browse the repository at this point in the history
The big field polygon refactoring
  • Loading branch information
Tensuko authored Jan 26, 2025
2 parents 34b5a0c + ec95bb3 commit 0bf2a5b
Show file tree
Hide file tree
Showing 46 changed files with 536 additions and 260 deletions.
2 changes: 2 additions & 0 deletions config/InfoTexts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<InfoText name="ERROR_WRONG_MISSION_FRUIT_TYPE" text="errorWrongMissionFruitType" class="AIMessageErrorWrongMissionFruitType"/>
<InfoText name="ERROR_PALLETS_ARE_FULL" text="errorPalletsAreFull" class="pdlc_premiumExpansion.AIMessageErrorPalletsFull"/>
<InfoText name="ERROR_PALLETS_ARE_EMPTY" text="errorPalletsAreEmpty" class="pdlc_premiumExpansion.AIMessageErrorNoPalletsLoaded"/>
<InfoText name="ERROR_TOO_FAR_FROM_FIELD" text="errorTooFarFromField" class="AIMessageErrorTooFarFromField"/>

<InfoText name="IS_STUCK" text="isStuck"/>
<InfoText name="BLOCKED_BY_OBJECT" text="blockedByObject" class="AIMessageErrorBlockedByObject"/>
Expand All @@ -41,4 +42,5 @@
<InfoText name="WORK_FINISHED" text="workFinished" hasFinished="true" event="onCpFinished" class="AIMessageSuccessFinishedJob"/>
<InfoText name="DRIVING_TO_COMBINE" text="drivingToCombine"/>
<InfoText name="DRIVING_TO_SELF_UNLOAD" text="drivingToSelfUnload"/>
<InfoText name="WAITING_FOR_FIELD_BOUNDARY_DETECTION" text="waitingForFieldBoundaryDetection"/>
</InfoTexts>
20 changes: 16 additions & 4 deletions config/MasterTranslations.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@
<Text language="de"><![CDATA[Konnte keinen Kurs generieren. Bitte versuchen es mit anderen Einstellungen.]]></Text>
<Text language="en"><![CDATA[Could not generate course. Please try other settings.]]></Text>
</Translation>
<Translation name="CP_error_field_detection_still_running">
<Text language="de"><![CDATA[Felderkennung läuft noch.]]></Text>
<Text language="en"><![CDATA[Field detection still running.]]></Text>
</Translation>
<Translation name="CP_error_field_detection_failed">
<Text language="de"><![CDATA[Felderkennung fehlgeschlagen.]]></Text>
<Text language="en"><![CDATA[Field detection failed.]]></Text>
</Translation>
<Translation name="CP_error_not_on_field">
<Text language="de"><![CDATA[Ziel ist nicht auf einem Feld.]]></Text>
<Text language="en"><![CDATA[Target is not on a field.]]></Text>
Expand All @@ -67,10 +75,6 @@
<Text language="de"><![CDATA[Feldabladeposition ist zu weit vom Feld entfernt.]]></Text>
<Text language="en"><![CDATA[Field unload position is too far away from the field.]]></Text>
</Translation>
<Translation name="CP_error_vehicle_too_far_away_from_field">
<Text language="de"><![CDATA[Fahrzeug ist zu weit vom Feld entfernt.]]></Text>
<Text language="en"><![CDATA[Vehicle is too far away from the field.]]></Text>
</Translation>
<Translation name="CP_error_no_bunkerSilo_found">
<Text language="de"><![CDATA[Kein Silo gefunden.]]></Text>
<Text language="en"><![CDATA[No silo found.]]></Text>
Expand Down Expand Up @@ -385,6 +389,10 @@
<Text language="de"><![CDATA[Helfer %s hat die Arbeit gestoppt - Falsche Frucht für die Mission ausgewählt!]]></Text>
<Text language="en"><![CDATA[AI worker %s has stopped work unexpectedly - wrong fill type for mission selected!]]></Text>
</Translation>
<Translation name="CP_ai_messageErrorTooFarFromField">
<Text language="de"><![CDATA[Fahrzeug ist zu weit vom Feld entfernt.]]></Text>
<Text language="en"><![CDATA[Vehicle is too far away from the field.]]></Text>
</Translation>
</Category>
<Category name="AI fieldwork task descriptions">
<Translation name="CP_ai_taskDescriptionAttachHeader">
Expand Down Expand Up @@ -1720,6 +1728,10 @@ The course is saved automatically on closing of the editor and overrides the sel
<Text language="de"><![CDATA[ÜLW entladen]]></Text>
<Text language="en"><![CDATA[Driving to trailer]]></Text>
</Translation>
<Translation name="CP_infoTexts_waitingForFieldBoundaryDetection">
<Text language="de"><![CDATA[Felderkennung]]></Text>
<Text language="en"><![CDATA[Field detection]]></Text>
</Translation>
<Translation name="CP_infoTexts_outOfMoney">
<Text language="de"><![CDATA[Kein Geld mehr]]></Text>
<Text language="en"><![CDATA[Out of money]]></Text>
Expand Down
16 changes: 0 additions & 16 deletions scripts/Course.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,6 @@ function Course:getVehicle()
return self.vehicle
end

function Course:setFieldPolygon(polygon)
self.fieldPolygon = polygon
end

-- The field polygon used to generate the course
function Course:getFieldPolygon()
local i = 1
while self.fieldPolygon == nil and i < self:getNumberOfWaypoints() do
CpUtil.debugVehicle(CpDebug.DBG_COURSES, self.vehicle, 'Field polygon not found, regenerating it (%d).', i)
local px, _, pz = self:getWaypointPosition(i)
self.fieldPolygon = CpFieldUtil.getFieldPolygonAtWorldPosition(px, pz)
i = i + 1
end
return self.fieldPolygon
end

function Course:getName()
return self.name
end
Expand Down
8 changes: 8 additions & 0 deletions scripts/ai/AIMessages.lua
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ function AIMessageErrorWrongMissionFruitType:getI18NText()
return g_i18n:getText("CP_ai_messageErrorWrongMissionFruitType")
end

---@class AIMessageErrorTooFarFromField
AIMessageErrorTooFarFromField = CpObject(AIMessage, AIMessage.new)
AIMessageErrorTooFarFromField.name = "CP_ERROR_TOO_FAR_FROM_FIELD"
function AIMessageErrorTooFarFromField:getI18NText()
return g_i18n:getText("CP_ai_messageErrorTooFarFromField")
end

CpAIMessages = {}
function CpAIMessages.register()
local function register(messageClass)
Expand All @@ -67,6 +74,7 @@ function CpAIMessages.register()
register(AIMessageErrorCutterNotSupported)
register(AIMessageErrorAutomaticCutterAttachNotActive)
register(AIMessageErrorWrongMissionFruitType)
register(AIMessageErrorTooFarFromField)
end

--- Another ugly hack, as the giants code to get the message index in mp isn't working ..
Expand Down
62 changes: 58 additions & 4 deletions scripts/ai/jobs/CpAIJob.lua
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ end

--- Updates the parameter values.
function CpAIJob:applyCurrentState(vehicle, mission, farmId, isDirectStart)
-- the only thing this does, is setting self.isDirectStart
AIJob.applyCurrentState(self, vehicle, mission, farmId, isDirectStart)
self.vehicleParameter:setVehicle(vehicle)
if not self.cpJobParameters or not self.cpJobParameters.startPosition then
Expand Down Expand Up @@ -233,6 +234,7 @@ function CpAIJob:setValues()
end

--- Is the job valid?
---@param farmId number not used
function CpAIJob:validate(farmId)
--- TODO_25
-- self:setParamterValid(true)
Expand All @@ -246,6 +248,62 @@ function CpAIJob:validate(farmId)
return isValid, errorMessage
end

--- Start an asynchronous field boundary detection. Results are delivered by the callback
--- onFieldBoundaryDetectionFinished(vehicle, fieldPolygon, islandPolygons)
--- If the field position hasn't changed since the last call, the detection is skipped and this returns true.
--- In that case, the polygon from the previous run is still available from vehicle:cpGetFieldPolygon()
---@return boolean, boolean, string true if we already have a field boundary false otherwise,
--- second boolean true if the detection is still running false on error
--- error message
function CpAIJob:detectFieldBoundary()
local vehicle = self.vehicleParameter:getVehicle()

local tx, tz = self.cpJobParameters.fieldPosition:getPosition()
if tx == nil or tz == nil then
return false, false, g_i18n:getText("CP_error_not_on_field")
end
if vehicle:cpIsFieldBoundaryDetectionRunning() then
return false, false, g_i18n:getText("CP_error_field_detection_still_running")
end
local x, z = vehicle:cpGetFieldPosition()
if x == tx and z == tz then
self:debug('Field position still at %.1f/%.1f, do not detect field boundary again', tx, tz)
return true, false, ''
end
self:debug('Field position changed to %.1f/%.1f, start field boundary detection', tx, tz)
self.foundVines = nil

vehicle:cpDetectFieldBoundary(tx, tz, self, self.onFieldBoundaryDetectionFinished)
-- TODO: return false and nothing, as the detection is still running?
return false, true, g_i18n:getText('CP_error_field_detection_still_running')
end

function CpAIJob:onFieldBoundaryDetectionFinished(vehicle, fieldPolygon, islandPolygons)
-- override in the derived classes to handle the detected field boundary
end

--- If registered, call the field boundary detection finished callback. This is to notify the frame
--- at the end of the async field detection.
--- It'll also return the result as a synchronous validate call would, and as the frame expects it, in case
--- someone calls the registered callback directly from validate()
---@return boolean isValid, string errorText
function CpAIJob:callFieldBoundaryDetectionFinishedCallback(isValid, errorTextName)
local c = self.onFieldBoundaryDetectionFinishedCallback
local errorText = errorTextName and g_i18n:getText(errorTextName) or ''
if c and c.object and c.func then
c.func(c.object, isValid, errorText)
end
return isValid, errorText
end

--- Register a callback for the field boundary detection finished event.
--- @param object table object to call the function on
--- @param func function function to call func(boolean isValid, string|nil errorTextName), errorTextName is the
--- name of the text in MasterTranslations.xml
function CpAIJob:registerFieldBoundaryDetectionCallback(object, func)
self.onFieldBoundaryDetectionFinishedCallback = {object = object, func = func}
end

function CpAIJob:getIsStartable(connection)

local vehicle = self.vehicleParameter:getVehicle()
Expand Down Expand Up @@ -353,10 +411,6 @@ function CpAIJob:getCpJobParameters()
return self.cpJobParameters
end

function CpAIJob:getFieldPolygon()
return self.fieldPolygon
end

function CpAIJob:setFieldPolygon(polygon)
self.fieldPolygon = polygon
end
Expand Down
45 changes: 11 additions & 34 deletions scripts/ai/jobs/CpAIJobBaleFinder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
CpAIJobBaleFinder = CpObject(CpAIJob)
CpAIJobBaleFinder.name = "BALE_FINDER_CP"
CpAIJobBaleFinder.jobName = "CP_job_baleCollect"
CpAIJobBaleFinder.minStartDistanceToField = 20
function CpAIJobBaleFinder:init(isServer)
CpAIJob.init(self, isServer)
self.selectedFieldPlot = FieldPlot(true)
Expand All @@ -28,10 +27,9 @@ function CpAIJobBaleFinder:getIsAvailableForVehicle(vehicle, cpJobsAllowed)
end

function CpAIJobBaleFinder:getCanStartJob()
return self:getFieldPolygon() ~= nil
return self:getVehicle():cpGetFieldPolygon() ~= nil
end


function CpAIJobBaleFinder:applyCurrentState(vehicle, mission, farmId, isDirectStart, isStartPositionInvalid)
CpAIJob.applyCurrentState(self, vehicle, mission, farmId, isDirectStart)
self.cpJobParameters:validateSettings()
Expand All @@ -53,7 +51,7 @@ end

--- Called when parameters change, scan field
function CpAIJobBaleFinder:validate(farmId)
local isValid, errorMessage = CpAIJob.validate(self, farmId)
local isValid, isRunning, errorMessage = CpAIJob.validate(self, farmId)
if not isValid then
return isValid, errorMessage
end
Expand All @@ -64,41 +62,20 @@ function CpAIJobBaleFinder:validate(farmId)
--------------------------------------------------------------
--- Validate field setup
--------------------------------------------------------------

isValid, errorMessage = self:validateFieldPosition(isValid, errorMessage)
local fieldPolygon = self:getFieldPolygon()
--------------------------------------------------------------
--- Validate start distance to field, if started with the hud
--------------------------------------------------------------
if isValid and self.isDirectStart and fieldPolygon then
--- Checks the distance for starting with the hud, as a safety check.
--- Firstly check, if the vehicle is near the field.
local x, _, z = getWorldTranslation(vehicle.rootNode)
isValid = CpMathUtil.isPointInPolygon(fieldPolygon, x, z) or
CpMathUtil.getClosestDistanceToPolygonEdge(fieldPolygon, x, z) < self.minStartDistanceToField
if not isValid then
return false, g_i18n:getText("CP_error_vehicle_too_far_away_from_field")
end
end


return isValid, errorMessage
isValid, isRunning, errorMessage = self:detectFieldBoundary(isValid, errorMessage)
-- if the field detection is still running, it's ok
return isValid or isRunning, errorMessage
end

function CpAIJobBaleFinder:validateFieldPosition(isValid, errorMessage)
local tx, tz = self.cpJobParameters.fieldPosition:getPosition()
if tx == nil or tz == nil then
return false, g_i18n:getText("CP_error_not_on_field")
end
local fieldPolygon, _ = CpFieldUtil.getFieldPolygonAtWorldPosition(tx, tz)
self:setFieldPolygon(fieldPolygon)
if fieldPolygon then
function CpAIJobBaleFinder:onFieldBoundaryDetectionFinished(vehicle, fieldPolygon, islandPolygons)
if fieldPolygon then
self.selectedFieldPlot:setWaypoints(fieldPolygon)
self.selectedFieldPlot:setVisible(true)
self.selectedFieldPlot:setVisible(true)
self:callFieldBoundaryDetectionFinishedCallback(true)
else
return false, g_i18n:getText("CP_error_not_on_field")
self.selectedFieldPlot:setVisible(false)
self:callFieldBoundaryDetectionFinishedCallback(false, 'CP_error_field_detection_failed')
end
return isValid, errorMessage
end

function CpAIJobBaleFinder:draw(map, isOverviewMap)
Expand Down
Loading

0 comments on commit 0bf2a5b

Please sign in to comment.