Skip to content

Commit 3241e25

Browse files
author
Peter Vaiko
committed
fix: harvester headland turn
1 parent ad19c63 commit 3241e25

3 files changed

Lines changed: 45 additions & 87 deletions

File tree

config/VehicleConfigurations.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ You can define the following custom settings:
8080
- lowerEarly: boolean
8181
Similar to the above, the default behavior when starting to work is lower the implement when the back of the
8282
work area reaches to row start/field edge. Setting this to true will make Courseplay lower the implement as
83-
soon as the the front reaches the row start/field edge, avoiding unworked patches with irregular implement
83+
soon as the front reaches the row start/field edge, avoiding unworked patches with irregular implement
8484
work areas such as a plow.
8585
8686
- useVehicleSizeForMarkers: boolean

scripts/ai/turns/AITurn.lua

Lines changed: 27 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,6 @@ function KTurn:onWaypointPassed(ix, course)
378378
end
379379

380380
function KTurn:startTurn()
381-
AITurn.startTurn(self)
382381
self.state = self.states.FORWARD
383382
end
384383

@@ -438,87 +437,6 @@ function KTurn:turn(dt)
438437
return gx, gz, moveForwards, maxSpeed
439438
end
440439

441-
--[[
442-
Headland turn for combines:
443-
1. drive forward to the field edge or the headland path edge
444-
2. start turning forward
445-
3. reverse straight and then align with the direction after the
446-
corner while reversing
447-
4. forward to the turn start to continue on headland
448-
]]
449-
---@class CombineHeadlandTurn : AITurn
450-
CombineHeadlandTurn = CpObject(AITurn)
451-
452-
---@param driveStrategy AIDriveStrategyFieldWorkCourse
453-
---@param turnContext TurnContext
454-
function CombineHeadlandTurn:init(vehicle, driveStrategy, ppc, proximityController, turnContext)
455-
AITurn.init(self, vehicle, driveStrategy, ppc, proximityController, turnContext, 'CombineHeadlandTurn')
456-
self:addState('FORWARD')
457-
self:addState('REVERSE_STRAIGHT')
458-
self:addState('REVERSE_ARC')
459-
self.turningRadius = AIUtil.getTurningRadius(self.vehicle)
460-
self.cornerAngleToTurn = turnContext:getCornerAngleToTurn()
461-
-- half the turn angle but not less than 45
462-
self.angleToTurnInReverse = math.max(math.pi / 4, math.abs(self.cornerAngleToTurn / 2))
463-
self.dxToStartReverseTurn = self.turningRadius - math.abs(self.turningRadius - self.turningRadius * math.cos(self.cornerAngleToTurn))
464-
end
465-
466-
function CombineHeadlandTurn:startTurn()
467-
self.state = self.states.FORWARD
468-
self:debug('Starting combine headland turn')
469-
end
470-
471-
function CombineHeadlandTurn:onWaypointChange(ix, course)
472-
-- nothing to do
473-
end
474-
475-
function CombineHeadlandTurn:onWaypointPassed(ix, course)
476-
-- nothing to do, especially because the row finishing course is still active in the PPC and we may
477-
-- pass the last waypoint which causes the turn to end and return to field work
478-
end
479-
480-
function CombineHeadlandTurn:turn(dt)
481-
local gx, gz, moveForwards, maxSpeed = AITurn.turn(self)
482-
local dx, _, dz = self.turnContext:getLocalPositionFromTurnEnd(self.vehicle:getAIDirectionNode())
483-
local angleToTurnEnd = math.abs(self.turnContext:getAngleToTurnEndDirection(self.vehicle:getAIDirectionNode()))
484-
if self.state == self.states.FORWARD then
485-
maxSpeed = self:getForwardSpeed()
486-
moveForwards = true
487-
if angleToTurnEnd > self.angleToTurnInReverse then
488-
--and not self.turnContext:isLateralDistanceLess(dx, self.dxToStartReverseTurn) then
489-
-- full turn towards the turn end direction
490-
gx, gz = self:getGoalPointForTurn(moveForwards, self.turnContext:isLeftTurn())
491-
else
492-
-- reverse until we can make turn to the turn end point
493-
self.state = self.states.REVERSE_STRAIGHT
494-
self:debug('Combine headland turn start reversing straight')
495-
end
496-
497-
elseif self.state == self.states.REVERSE_STRAIGHT then
498-
maxSpeed = self:getReverseSpeed()
499-
moveForwards = false
500-
gx, gz = self:getGoalPointForTurn(moveForwards, nil)
501-
if math.abs(dx) < 0.2 then
502-
self.state = self.states.REVERSE_ARC
503-
self:debug('Combine headland turn start reversing arc')
504-
end
505-
506-
elseif self.state == self.states.REVERSE_ARC then
507-
maxSpeed = self:getReverseSpeed()
508-
moveForwards = false
509-
gx, gz = self:getGoalPointForTurn(moveForwards, not self.turnContext:isLeftTurn())
510-
if angleToTurnEnd < math.rad(20) then
511-
self.state = self.states.ENDING_TURN
512-
self:debug('Combine headland turn forwarding again')
513-
-- lower implements here unconditionally (regardless of the direction, self:endTurn() would wait until we
514-
-- are pointing to the turn target direction)
515-
self.driveStrategy:lowerImplements()
516-
self:resumeFieldworkAfterTurn(self.turnContext.turnEndWpIx)
517-
end
518-
end
519-
return gx, gz, moveForwards, maxSpeed
520-
end
521-
522440
--[[
523441
A turn maneuver following a course (waypoints created by turn.lua)
524442
]]
@@ -586,7 +504,6 @@ end
586504
-- = if turn on field setting is off, use pathfinder turns if enabled in settings, calculated turns otherwise
587505
--
588506
function CourseTurn:startTurn()
589-
AITurn.startTurn(self)
590507
local canTurnOnField = AITurn.canTurnOnField(self.turnContext, self.vehicle, self.workWidth, self.turningRadius)
591508
if self.turnContext:isHeadlandCorner() then
592509
self:debug('Starting a headland corner turn')
@@ -750,7 +667,7 @@ function CourseTurn:generateCalculatedTurn()
750667
self.enableTightTurnOffset = true
751668
else
752669
turnManeuver = HeadlandCornerTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(),
753-
self.turningRadius, self.workWidth, self.reversingImplement, self.steeringLength)
670+
self.turningRadius, self.workWidth, self.reversingImplement, self.steeringLength)
754671
-- adjust turn course for tight turns only for headland corners by default
755672
self.forceTightTurnOffset = self.steeringLength > 0
756673
end
@@ -826,6 +743,32 @@ function CourseTurn:drawDebug()
826743
end
827744
end
828745

746+
--[[
747+
Headland turn for combines:
748+
1. drive forward to the field edge or the headland path edge
749+
2. start turning forward
750+
3. reverse straight and then align with the direction after the
751+
corner while reversing
752+
4. forward to the turn start to continue on headland
753+
]]
754+
---@class CombineHeadlandTurn : CourseTurn
755+
CombineHeadlandTurn = CpObject(CourseTurn)
756+
757+
function CombineHeadlandTurn:init(vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse,
758+
workWidth, name)
759+
CourseTurn.init(self, vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse,
760+
workWidth, name or 'CombineHeadlandTurn')
761+
end
762+
763+
function CombineHeadlandTurn:startTurn()
764+
self:debug('Starting a combine headland turn')
765+
self.turnCourse = ReedsSheppHeadlandTurn(self.vehicle, self.turnContext,
766+
self.turnContext.vehicleAtTurnStartNode, self.turningRadius):getCourse()
767+
self.state = self.states.TURNING
768+
self.ppc:setCourse(self.turnCourse)
769+
self.ppc:initialize(1)
770+
end
771+
829772
--- A turn maneuver to recover when the vehicle is blocked by an object (tree, fence, etc) during the turn
830773
--- This should only be initiated when the state is TURNING, so after the row is finished and before starting
831774
--- the new row.

scripts/ai/turns/TurnManeuver.lua

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ function AnalyticTurnManeuver:getDistanceToMoveBack(course, workWidth, distanceT
434434
-- the field, not only the center
435435
local headlandAngle = self.turnContext:getHeadlandAngle()
436436
distanceToFieldEdge = distanceToFieldEdge -
437-
-- exclude very sharp headland angles to prevent moving back ridiculously far
437+
-- exclude very sharp headland angles to prevent moving back ridiculously far
438438
((headlandAngle > math.deg(10) and headlandAngle < math.deg(170))
439439
and (workWidth / 2 / math.abs(math.tan(headlandAngle))) or 0)
440440
self:debug('dzMax=%.1f, workWidth=%.1f, spaceNeeded=%.1f, turnEndForwardOffset=%.1f, headlandAngle=%.1f, distanceToFieldEdge=%.1f', dzMax, workWidth,
@@ -465,7 +465,7 @@ end
465465
---@class LoopTurnManeuver : TurnManeuver
466466
LoopTurnManeuver = CpObject(DubinsTurnManeuver)
467467
function LoopTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius,
468-
workWidth, steeringLength)
468+
workWidth, steeringLength)
469469
self.debugPrefix = '(LoopTurn): '
470470
TurnManeuver.init(self, vehicle, turnContext, vehicleDirectionNode, turningRadius,
471471
workWidth, steeringLength)
@@ -552,6 +552,21 @@ function ReedsSheppTurnManeuver:findAnalyticPath(vehicleDirectionNode, startXOff
552552
return course
553553
end
554554

555+
---@class ReedsSheppHeadlandTurn : TurnManeuver
556+
ReedsSheppHeadlandTurn = CpObject(TurnManeuver)
557+
558+
--- This is a headland turn (~90 degrees) for non-towed harvesters with cutter on the front. Expected to be called
559+
--- just after the cutter finished the corner, that is, the harvester should drive forward in the original direction
560+
--- until there is no fruit left. It'll then do a quick 90 degree 3 point turn to align with the new direction.
561+
function ReedsSheppHeadlandTurn:init(vehicle, turnContext, vehicleDirectionNode, turningRadius)
562+
local solver = ReedsSheppSolver()
563+
-- use lateWorkStartNode since we covered the corner in the inbound direction already
564+
local path = PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, 0, 0,
565+
turnContext.lateWorkStartNode, 0, -turnContext.backMarkerDistance, turningRadius)
566+
self.course = Course.createFromAnalyticPath(vehicle, path, true)
567+
self.course:adjustForReversing(2)
568+
end
569+
555570
---@class TurnEndingManeuver : TurnManeuver
556571
TurnEndingManeuver = CpObject(TurnManeuver)
557572

0 commit comments

Comments
 (0)