Skip to content

Commit a77853f

Browse files
authored
Merge pull request #183 from Courseplay/79-tools-sometimes-stop-working-after-transition-from-headland-to-center-or-vice-versa
fix: headland/center transition
2 parents d27c57f + 6ab087d commit a77853f

5 files changed

Lines changed: 66 additions & 38 deletions

File tree

scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ AIDriveStrategyFieldWorkCourse.myStates = {
2828
WAITING_FOR_LOWER_DELAYED = {},
2929
WAITING_FOR_STOP = {},
3030
WAITING_FOR_WEATHER = {},
31-
TURNING = {showTurnContextDebug = true},
31+
TURNING = { showTurnContextDebug = true },
3232
TEMPORARY = {},
3333
RETURNING_TO_START = {},
34-
DRIVING_TO_WORK_START_WAYPOINT = {showTurnContextDebug = true},
34+
DRIVING_TO_WORK_START_WAYPOINT = { showTurnContextDebug = true },
3535
}
3636

3737
AIDriveStrategyFieldWorkCourse.normalFillLevelFullPercentage = 99.5
@@ -94,7 +94,7 @@ function AIDriveStrategyFieldWorkCourse:start(course, startIx, jobParameters)
9494
--- Store a reference to the original generated course
9595
self.originalGeneratedFieldWorkCourse = self.vehicle:getFieldWorkCourse()
9696

97-
if self.fieldPolygon == nil then
97+
if self.fieldPolygon == nil then
9898
self:debug("No field polygon received, so regenerate it by the course.")
9999
self.fieldPolygon = self.fieldWorkCourse:getFieldPolygon()
100100
end
@@ -114,7 +114,7 @@ end
114114
function AIDriveStrategyFieldWorkCourse:update(dt)
115115
AIDriveStrategyCourse.update(self, dt)
116116
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
117-
if self.state == self.states.TURNING then
117+
if self.state == self.states.TURNING then
118118
if self.aiTurn then
119119
self.aiTurn:drawDebug()
120120
end
@@ -188,7 +188,9 @@ function AIDriveStrategyFieldWorkCourse:getDriveData(dt, vX, vY, vZ)
188188
self:setMaxSpeed(turnMaxSpeed)
189189
-- if turn tells us which way to go, use that, otherwise just do whatever PPC tells us
190190
gx, gz = turnGx or gx, turnGz or gz
191-
if turnMoveForwards ~= nil then moveForwards = turnMoveForwards end
191+
if turnMoveForwards ~= nil then
192+
moveForwards = turnMoveForwards
193+
end
192194
elseif self.state == self.states.RETURNING_TO_START then
193195
local isReadyToDrive, blockingVehicle = self.vehicle:getIsAIReadyToDrive()
194196
if isReadyToDrive or not self.waitingForPrepare:get() then
@@ -263,7 +265,8 @@ function AIDriveStrategyFieldWorkCourse:initializeImplementControllers(vehicle)
263265

264266
self:addImplementController(vehicle, PickupController, Pickup, defaultDisabledStates)
265267
self:addImplementController(vehicle, SprayerController, Sprayer, {})
266-
self:addImplementController(vehicle, CutterController, Cutter, {}) --- Makes sure the cutter timer gets reset always.
268+
self:addImplementController(vehicle, CutterController, Cutter, {})
269+
--- Makes sure the cutter timer gets reset always.
267270
self:addImplementController(vehicle, StonePickerController, StonePicker, defaultDisabledStates)
268271

269272
self:addImplementController(vehicle, FoldableController, Foldable, {})
@@ -312,7 +315,9 @@ function AIDriveStrategyFieldWorkCourse:shouldRaiseThisImplement(object, turnSta
312315
local aiFrontMarker, _, aiBackMarker = WorkWidthUtil.getAIMarkers(object, true)
313316
-- if something (like a combine) does not have an AI marker it should not prevent from raising other implements
314317
-- like the header, which does have markers), therefore, return true here
315-
if not aiBackMarker or not aiFrontMarker then return true end
318+
if not aiBackMarker or not aiFrontMarker then
319+
return true
320+
end
316321
local marker = self:getImplementRaiseLate() and aiBackMarker or aiFrontMarker
317322
-- turn start node in the back marker node's coordinate system
318323
local _, _, dz = localToLocal(marker, turnStartNode, 0, 0, 0)
@@ -355,7 +360,9 @@ end
355360
--- in meters (<0) when driving forward, nil when driving backwards.
356361
function AIDriveStrategyFieldWorkCourse:shouldLowerThisImplement(object, workStartNode, reversing)
357362
local aiLeftMarker, aiRightMarker, aiBackMarker = WorkWidthUtil.getAIMarkers(object, true)
358-
if not aiLeftMarker then return false, false, nil end
363+
if not aiLeftMarker then
364+
return false, false, nil
365+
end
359366
local dxLeft, _, dzLeft = localToLocal(aiLeftMarker, workStartNode, 0, 0, 0)
360367
local dxRight, _, dzRight = localToLocal(aiRightMarker, workStartNode, 0, 0, 0)
361368
local dxBack, _, dzBack = localToLocal(aiBackMarker, workStartNode, 0, 0, 0)
@@ -408,7 +415,9 @@ end
408415

409416
function AIDriveStrategyFieldWorkCourse:isThisImplementAligned(object, node)
410417
local aiFrontMarker, _, _ = WorkWidthUtil.getAIMarkers(object, true)
411-
if not aiFrontMarker then return true end
418+
if not aiFrontMarker then
419+
return true
420+
end
412421
return CpMathUtil.isSameDirection(aiFrontMarker, node, 5)
413422
end
414423

@@ -426,7 +435,8 @@ function AIDriveStrategyFieldWorkCourse:onWaypointChange(ix, course)
426435
end
427436
self:startTurn(ix)
428437
elseif self.state == self.states.WORKING then
429-
if self.course:isOnConnectingPath(ix + 1) or self.course:shouldUsePathfinderToNextWaypoint(ix) then
438+
if (self.course:isOnConnectingPath(ix + 1) and not self.course:isOnConnectingPath(ix)) or
439+
self.course:shouldUsePathfinderToNextWaypoint(ix) then
430440
local fm, bm = self:getFrontAndBackMarkers()
431441
self.turnContext = RowStartOrFinishContext(self.vehicle, self.course, ix, ix, self.turnNodes, self:getWorkWidth(),
432442
fm, bm, 0, 0)
@@ -626,7 +636,6 @@ end
626636
-----------------------------------------------------------------------------------------------------------------------
627637
function AIDriveStrategyFieldWorkCourse:startPathfindingToNextWaypoint(ix)
628638
self:debug('start pathfinding to waypoint %d', ix + 1)
629-
self:raiseImplements()
630639
local fm, bm = self:getFrontAndBackMarkers()
631640
self.turnContext = RowStartOrFinishContext(self.vehicle, self.fieldWorkCourse, ix + 1, ix + 1,
632641
self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset())
@@ -643,10 +652,17 @@ function AIDriveStrategyFieldWorkCourse:startPathfindingToNextWaypoint(ix)
643652
self.pathfinderController:findPathToNode(context, targetNode, 0, zOffset)
644653
end
645654

646-
function AIDriveStrategyFieldWorkCourse:onPathfindingFailedToNextWaypoint()
647-
self:debug('Pathfinding to next waypoint failed, continue directly at waypoint %d', self.waypointToContinueOnFailedPathfinding)
648-
self.state = self.states.WORKING
649-
self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding)
655+
function AIDriveStrategyFieldWorkCourse:onPathfindingFailedToNextWaypoint(controller, lastContext, wasLastRetry, currentRetryAttempt)
656+
if wasLastRetry then
657+
self:debug('Pathfinding to next waypoint failed again, continue directly at waypoint %d', self.waypointToContinueOnFailedPathfinding)
658+
self:startWaitingForLower()
659+
self:lowerImplements()
660+
self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding)
661+
else
662+
self:debug('Pathfinding to next waypoint failed once, retry with disabled collisions')
663+
lastContext:collisionMask(0)
664+
controller:retry(lastContext)
665+
end
650666
end
651667

652668
function AIDriveStrategyFieldWorkCourse:onPathfindingDoneToNextWaypoint(controller, success, course, goalNodeInvalid)
@@ -663,22 +679,24 @@ end
663679
-----------------------------------------------------------------------------------------------------------------------
664680
function AIDriveStrategyFieldWorkCourse:startConnectingPath(ix)
665681
-- ix was the last waypoint to work before the connecting path, ix + 1 is the first on the connecting path
666-
self:debug('on a connecting path now at waypoint %d, raising implements.', ix + 1)
667-
self:raiseImplements()
682+
self:debug('Row finished before starting on a connecting path at waypoint %d.', ix + 1)
668683
-- gather the connecting path waypoints
669684
local connectingPath = {}
670685
local targetWaypointIx
671686
for i = ix + 1, self.fieldWorkCourse:getNumberOfWaypoints() do
672687
if self.fieldWorkCourse:isOnConnectingPath(i) then
673688
local x, _, z = self.fieldWorkCourse:getWaypointPosition(i)
674-
table.insert(connectingPath, {x = x, z = z})
689+
table.insert(connectingPath, { x = x, z = z })
675690
else
676691
targetWaypointIx = i
677692
break
678693
end
679694
end
680695
if targetWaypointIx == nil then
681-
self:onPathfindingFailedToConnectingPathEnd()
696+
self:debug('Can\'t find end of connecting path, continuing work')
697+
self:startWaitingForLower()
698+
self:lowerImplements()
699+
self:startCourse(self.fieldWorkCourse, ix + 1)
682700
else
683701
-- set up the turn context for the work starter to use when the pathfinding succeeds
684702
local fm, bm = self:getFrontAndBackMarkers()
@@ -688,28 +706,34 @@ function AIDriveStrategyFieldWorkCourse:startConnectingPath(ix)
688706
local targetNode, zOffset = self.turnContext:getTurnEndNodeAndOffsets(steeringLength)
689707
local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding())
690708
context:preferredPath(connectingPath):mustBeAccurate(true)
691-
self.waypointToContinueOnFailedPathfinding = ix + 1
709+
self.rawConnectingPath = Course(self.vehicle, connectingPath, true)
692710
self.pathfinderController:registerListeners(self, self.onPathfindingDoneToConnectingPathEnd,
693711
self.onPathfindingFailedToConnectingPathEnd)
694712
self:debug('Connecting path has %d waypoints, start pathfinding to target waypoint %d, zOffset %.1f',
695713
#connectingPath, targetWaypointIx, zOffset)
696714
self.state = self.states.WAITING_FOR_PATHFINDER
697-
self.pathfinderController:findPathToNode(context, targetNode, 0, zOffset)
715+
self.pathfinderController:findPathToNode(context, targetNode, 0, zOffset, 1)
698716
end
699717
end
700718

701-
function AIDriveStrategyFieldWorkCourse:onPathfindingFailedToConnectingPathEnd()
702-
self:debug('Pathfinding to end of connecting path failed, use the connecting path as is')
703-
self.state = self.states.WORKING
704-
self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding)
719+
function AIDriveStrategyFieldWorkCourse:onPathfindingFailedToConnectingPathEnd(controller, lastContext, wasLastRetry, currentRetryAttempt)
720+
if wasLastRetry then
721+
self:debug('Pathfinding to end of connecting path failed again, use the connecting path as is')
722+
self:startCourseToWorkStart(self.rawConnectingPath)
723+
else
724+
self:debug('Pathfinding to end of connecting path failed once, retry with disabled collisions')
725+
lastContext:collisionMask(0)
726+
controller:retry(lastContext)
727+
end
705728
end
706729

707730
function AIDriveStrategyFieldWorkCourse:onPathfindingDoneToConnectingPathEnd(controller, success, course, goalNodeInvalid)
708731
if success then
709732
self:debug('Pathfinding to end of connecting path finished')
710733
self:startCourseToWorkStart(course)
711734
else
712-
self:onPathfindingFailedToConnectingPathEnd()
735+
self:debug('Pathfinding to end of connecting path failed, use the connecting path as is')
736+
self:startCourseToWorkStart(self.rawConnectingPath)
713737
end
714738
end
715739

@@ -732,7 +756,7 @@ function AIDriveStrategyFieldWorkCourse:setAllStaticParameters()
732756
end
733757

734758
function AIDriveStrategyFieldWorkCourse:setFieldPolygon(polygon)
735-
self.fieldPolygon = polygon
759+
self.fieldPolygon = polygon
736760
end
737761

738762
-----------------------------------------------------------------------------------------------------------------------

scripts/dev/DevHelper.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ function DevHelper:update()
9191
self.data.collidingShapes = {}
9292
overlapBox(self.data.x, self.data.y + 0.2 + DevHelper.overlapBoxHeight / 2, self.data.z, 0, self.yRot, 0,
9393
DevHelper.overlapBoxWidth / 2, DevHelper.overlapBoxHeight / 2, DevHelper.overlapBoxLength / 2,
94-
"overlapBoxCallback", self, collisionMask, true, true, true)
94+
"overlapBoxCallback", self, collisionMask, true, true, true, true)
9595

9696
end
9797

@@ -115,7 +115,7 @@ function DevHelper:overlapBoxCallback(transformId, subShapeIndex)
115115
if collidingObject:isa(Bale) then
116116
text = text .. ' Bale ' .. tostring(collidingObject.id) .. ' ' .. tostring(collidingObject.nodeId)
117117
else
118-
text = text .. ' ' .. collidingObject.getName and collidingObject:getName() or 'N/A'
118+
text = text .. ' ' .. (collidingObject.getName and collidingObject:getName() or 'N/A')
119119
end
120120
end
121121
end

scripts/pathfinder/PathfinderConstraints.lua

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ function PathfinderConstraints:init(context)
7272
self.ignoreTrailerAtStartRange = context._ignoreTrailerAtStartRange or 0
7373
self.initialMaxFruitPercent = self.maxFruitPercent
7474
self.initialOffFieldPenalty = self.offFieldPenalty
75+
self.collisionMask = context._collisionMask
7576
self.strictMode = false
7677
self:resetCounts()
7778
local areaToAvoidText = self.areaToAvoid and
@@ -204,8 +205,8 @@ function PathfinderConstraints:isValidNode(node, ignoreTrailer, offFieldValid)
204205
node.x, -node.y, CpMathUtil.angleToGame(node.t), 0.5)
205206

206207
-- for debug purposes only, store validity info on node
207-
node.collidingShapes = PathfinderUtil.collisionDetector:findCollidingShapes(
208-
PathfinderUtil.helperNode, self.vehicleData, self.vehiclesToIgnore, self.objectsToIgnore, self.ignoreFruitHeaps)
208+
node.collidingShapes = PathfinderUtil.collisionDetector:findCollidingShapes(PathfinderUtil.helperNode,
209+
self.vehicleData, self.vehiclesToIgnore, self.objectsToIgnore, self.ignoreFruitHeaps, self.collisionMask)
209210
ignoreTrailer = ignoreTrailer or node.d < self.ignoreTrailerAtStartRange
210211
if self.vehicleData.trailer and not ignoreTrailer then
211212
-- now check the trailer or towed implement
@@ -217,7 +218,7 @@ function PathfinderConstraints:isValidNode(node, ignoreTrailer, offFieldValid)
217218

218219
node.collidingShapes = node.collidingShapes + PathfinderUtil.collisionDetector:findCollidingShapes(
219220
PathfinderUtil.helperNode, self.vehicleData.trailerRectangle, self.vehiclesToIgnore,
220-
self.objectsToIgnore, self.ignoreFruitHeaps)
221+
self.objectsToIgnore, self.ignoreFruitHeaps, self.collisionMask)
221222
if node.collidingShapes > 0 then
222223
self.trailerCollisionNodeCount = self.trailerCollisionNodeCount + 1
223224
end

scripts/pathfinder/PathfinderContext.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ PathfinderContext.attributesToDefaultValue = {
103103
["ignoreTrailerAtStartRange"] = 0,
104104
-- Maximum number of iterations before the hybrid A* pathfinding gives up. The default works well
105105
-- for normal sized fields, on bigger maps with huge fields you may want to double or triple this
106-
["maxIterations"] = HybridAStar.defaultMaxIterations
106+
["maxIterations"] = HybridAStar.defaultMaxIterations,
107+
-- Collision mask to use, to disable collisions completely, set to 0objectsToIgnore
108+
["collisionMask"] = CpUtil.getDefaultCollisionFlags() + CollisionFlag.TERRAIN_DELTA
107109
}
108110

109111
function PathfinderContext:init(vehicle)

scripts/pathfinder/PathfinderUtil.lua

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,10 @@ PathfinderUtil.CollisionDetector = CpObject()
245245
--- Nodes of a trigger for example, that will be ignored as collision.
246246
PathfinderUtil.CollisionDetector.NODES_TO_IGNORE = {}
247247

248-
function PathfinderUtil.CollisionDetector:init()
248+
function PathfinderUtil.CollisionDetector:init(collisionMask)
249249
self.vehiclesToIgnore = {}
250250
self.collidingShapes = 0
251+
self.collisionMask = collisionMask or CpUtil.getDefaultCollisionFlags()
251252
end
252253

253254
--- Adds a node which collision will be ignored global for every pathfinder.
@@ -318,7 +319,8 @@ function PathfinderUtil.CollisionDetector:overlapBoxCallback(transformId)
318319
self.collidingShapes = self.collidingShapes + 1
319320
end
320321

321-
function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData, vehiclesToIgnore, objectsToIgnore, ignoreFruitHeaps)
322+
function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData, vehiclesToIgnore, objectsToIgnore,
323+
ignoreFruitHeaps, collisionMask)
322324
self.vehiclesToIgnore = vehiclesToIgnore or {}
323325
self.objectsToIgnore = objectsToIgnore or {}
324326
self.vehicleData = vehicleData
@@ -343,9 +345,8 @@ function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData,
343345
self.collidingShapes = 0
344346
self.collidingShapesText = 'unknown'
345347

346-
local collisionMask = CpUtil.getDefaultCollisionFlags() + CollisionFlag.TERRAIN_DELTA
347-
348-
overlapBox(x, y + 0.2, z, xRot, yRot, zRot, width, 1, length, 'overlapBoxCallback', self, collisionMask, true, true, true)
348+
overlapBox(x, y + 0.2, z, xRot, yRot, zRot, width, 1, length, 'overlapBoxCallback', self, collisionMask,
349+
true, true, true, true)
349350

350351
if true and self.collidingShapes > 0 then
351352
table.insert(PathfinderUtil.overlapBoxes,

0 commit comments

Comments
 (0)