Skip to content

Commit 97ae250

Browse files
authored
Merge pull request #979 from Courseplay/972-pflug-sollte-nach-innen-pflugen-auf-vorgewende
refactor: plow rotation logic unified
2 parents 9418548 + ae0c219 commit 97ae250

8 files changed

Lines changed: 91 additions & 69 deletions

File tree

scripts/Course.lua

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,29 @@ function Course:isNextTurnLeft(ix)
635635
end
636636
end
637637

638+
--- Should the plow be rotated to the left at the waypoint ix?
639+
--- On the headland just check which side was worked last, on the center, check the direction of the next turn
640+
--- as at the first pass both sides are unworked and need some other indication on which side the plow should be
641+
function Course:shouldPlowBeOnTheLeft(ix)
642+
local plowShouldBeOnTheLeft
643+
if self:isOnHeadland(ix) then
644+
local clockwise = self:isOnClockwiseHeadland(ix)
645+
plowShouldBeOnTheLeft = not clockwise
646+
CpUtil.debugVehicle(CpDebug.DBG_TURN, self.vehicle, 'On a headland (clockwise %s), plow should be on the left %s', tostring(clockwise), tostring(plowShouldBeOnTheLeft))
647+
else
648+
local isNextTurnLeft = self:isNextTurnLeft(ix)
649+
if isNextTurnLeft == nil then
650+
-- don't know if left or right, so just use the last worked side
651+
plowShouldBeOnTheLeft = self:isLeftSideWorked(ix)
652+
CpUtil.debugVehicle(CpDebug.DBG_TURN, self.vehicle, 'On the center, next turn direction unknown, plow should be on the left %s', tostring(plowShouldBeOnTheLeft))
653+
else
654+
plowShouldBeOnTheLeft = not isNextTurnLeft
655+
CpUtil.debugVehicle(CpDebug.DBG_TURN, self.vehicle, 'On the center, plow should be on the left %s', tostring(plowShouldBeOnTheLeft))
656+
end
657+
end
658+
return plowShouldBeOnTheLeft
659+
end
660+
638661
function Course:getIxRollover(ix)
639662
if ix > #self.waypoints then
640663
return ix - #self.waypoints
@@ -1455,7 +1478,7 @@ end
14551478
local function calculateYRot(waypoints, i)
14561479
local x1, z1 = waypoints[i].x, waypoints[i].z
14571480
local x2, z2 = waypoints[i + 1].x, waypoints[i + 1].z
1458-
if (x1 - x2) == 0 and (z1 - z2) == 0 then
1481+
if (x1 - x2) == 0 and (z1 - z2) == 0 then
14591482
-- Divide by zero fix ..
14601483
return 0
14611484
end
@@ -1810,13 +1833,13 @@ function Course.MultiVehicleData.createFromStream(stream, nVehicles)
18101833
end
18111834

18121835
function Course.MultiVehicleData.getAllowedPositions(nMultiToolVehicles)
1813-
if nMultiToolVehicles == 2 then
1836+
if nMultiToolVehicles == 2 then
18141837
return {-1,1}
1815-
elseif nMultiToolVehicles == 3 then
1838+
elseif nMultiToolVehicles == 3 then
18161839
return {-1,0,1}
1817-
elseif nMultiToolVehicles == 4 then
1840+
elseif nMultiToolVehicles == 4 then
18181841
return {-2,-1,1,2}
1819-
elseif nMultiToolVehicles == 5 then
1842+
elseif nMultiToolVehicles == 5 then
18201843
return {-2,-1,0,1,2}
18211844
end
18221845
return {0}

scripts/ai/controllers/PlowController.lua

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function PlowController:init(vehicle, implement)
1212
-- the plow was not rotated into the working position before lowering. If that's the case, onLowering will
1313
-- rotate it but it needs to know which direction to lower, which, however is not known at that point
1414
-- anymore
15-
self.lastTurnIsLeftTurn = CpTemporaryObject()
15+
self.lastPlowSide = CpTemporaryObject()
1616
end
1717

1818

@@ -115,38 +115,38 @@ end
115115
-- TODO: this whole magic hack would not be necessary if we moved the actual lowering into onTurnEndProgress()
116116
function PlowController:onLowering()
117117
-- if we just turned (that is, not starting to work)
118-
local lastTurnIsLeftTurn = self.lastTurnIsLeftTurn:get()
119-
if lastTurnIsLeftTurn ~= nil and
118+
local lastPlowSide = self.lastPlowSide:get()
119+
if lastPlowSide ~= nil and
120120
self:isRotatablePlow() and not self:isFullyRotated() and not self:isRotationActive() then
121-
self:debug('Lowering, rotating plow to working position (last turn is left %s).', lastTurnIsLeftTurn)
121+
self:debug('Lowering, rotating plow to working position (plow in last turn was left %s).', lastPlowSide)
122122
-- rotation direction depends on the direction of the last turn
123-
self.implement:setRotationMax(lastTurnIsLeftTurn)
123+
self.implement:setRotationMax(lastPlowSide)
124124
end
125125
end
126126

127127
--- This is called in every loop when we approach the start of the row, the location where
128-
--- the plow must be lowered. Currently AIDriveStrategyFieldworkCourse takes care of the lowering,
128+
--- the plow must be lowered. Currently the WorkStarter takes care of the lowering,
129129
--- here we only make sure that the plow is rotated to the work position (from the center position)
130130
--- in time.
131131
---@param workStartNode number node where the work starts as calculated by TurnContext
132132
---@param reversing boolean driving in reverse now
133133
---@param shouldLower boolean the implement should be lowered as we are close to the work start (this
134134
--- should most likely be calculated here in the controller, but for now, we get it from an argument
135-
---@param isLeftTurn boolean is this a left turn?
136-
function PlowController:onTurnEndProgress(workStartNode, reversing, shouldLower, isLeftTurn)
137-
self.lastTurnIsLeftTurn:set(isLeftTurn or false, 2000)
135+
---@param shouldBeOnTheLeft boolean should the plow be turned to the left to be in the good position after the turn?
136+
function PlowController:onTurnEndProgress(workStartNode, reversing, shouldLower, shouldBeOnTheLeft)
137+
self.lastPlowSide:set(shouldBeOnTheLeft or false, 2000)
138138
if self:isRotatablePlow() and not self:isFullyRotated() and not self:isRotationActive() then
139139
-- more or less aligned with the first waypoint of the row, start rotating to working position
140140
if CpMathUtil.isSameDirection(self.implement.rootNode, workStartNode, 30) or shouldLower then
141141
if self.towed then
142142
-- let towed plows remain in the center position while reversing to the start of the row
143143
if not reversing then
144144
self:debug('Rotating towed plow to working position.')
145-
self.implement:setRotationMax(isLeftTurn)
145+
self.implement:setRotationMax(shouldBeOnTheLeft)
146146
end
147147
else
148148
self:debug('Rotating hitch-mounted plow to working position.')
149-
self.implement:setRotationMax(isLeftTurn)
149+
self.implement:setRotationMax(shouldBeOnTheLeft)
150150
end
151151
end
152152
end

scripts/ai/strategies/AIDriveStrategyCombineCourse.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1405,7 +1405,7 @@ function AIDriveStrategyCombineCourse:startTurn(ix)
14051405

14061406
self.turnContext = TurnContext(self.vehicle, self.course, ix, ix + 1, self.turnNodes, self:getWorkWidth(),
14071407
self.frontMarkerDistance, self.backMarkerDistance,
1408-
self:getTurnEndSideOffset(), self:getTurnEndForwardOffset())
1408+
self:getTurnEndSideOffset(self.course:isHeadlandTurnAtIx(ix + 1)), self:getTurnEndForwardOffset())
14091409

14101410
-- Combines drive special headland corner maneuvers, except potato and sugarbeet harvesters
14111411
if self.turnContext:isHeadlandCorner() then

scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ function AIDriveStrategyFieldWorkCourse:startTurn(ix)
392392
local fm, bm = self:getFrontAndBackMarkers()
393393
self.ppc:setShortLookaheadDistance()
394394
self.turnContext = TurnContext(self.vehicle, self.course, ix, ix + 1, self.turnNodes, self:getWorkWidth(), fm, bm,
395-
self:getTurnEndSideOffset(), self:getTurnEndForwardOffset())
395+
self:getTurnEndSideOffset(self.course:isHeadlandTurnAtIx(ix + 1)), self:getTurnEndForwardOffset())
396396
if AITurn.canMakeKTurn(self.vehicle, self.turnContext, self.workWidth, self:isTurnOnFieldActive()) then
397397
self.aiTurn = KTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, self.workWidth)
398398
else
@@ -488,7 +488,7 @@ function AIDriveStrategyFieldWorkCourse:startAlignmentTurn(fieldWorkCourse, star
488488
if alignmentCourse then
489489
local fm, bm = self:getFrontAndBackMarkers()
490490
self.turnContext = RowStartOrFinishContext(self.vehicle, fieldWorkCourse, startIx, startIx, self.turnNodes,
491-
self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset())
491+
self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(false), self:getTurnEndForwardOffset())
492492
self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, alignmentCourse)
493493
self.state = self.states.DRIVING_TO_WORK_START_WAYPOINT
494494
self:startCourse(self.workStarter:getCourse(), 1)
@@ -542,7 +542,7 @@ function AIDriveStrategyFieldWorkCourse:startPathfindingToNextWaypoint(ix)
542542
self:debug('start pathfinding to waypoint %d', ix + 1)
543543
local fm, bm = self:getFrontAndBackMarkers()
544544
self.turnContext = RowStartOrFinishContext(self.vehicle, self.fieldWorkCourse, ix + 1, ix + 1,
545-
self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset())
545+
self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(false), self:getTurnEndForwardOffset())
546546
local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle)
547547
local targetNode, zOffset = self.turnContext:getTurnEndNodeAndOffsets(steeringLength)
548548
local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding())
@@ -605,7 +605,7 @@ function AIDriveStrategyFieldWorkCourse:startConnectingPath(ix)
605605
-- set up the turn context for the work starter to use when the pathfinding succeeds
606606
local fm, bm = self:getFrontAndBackMarkers()
607607
self.turnContext = RowStartOrFinishContext(self.vehicle, self.fieldWorkCourse, targetWaypointIx, targetWaypointIx,
608-
self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset())
608+
self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(false), self:getTurnEndForwardOffset())
609609
local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle)
610610
local targetNode, zOffset = self.turnContext:getTurnEndNodeAndOffsets(steeringLength)
611611
local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding())
@@ -670,7 +670,7 @@ end
670670
-----------------------------------------------------------------------------------------------------------------------
671671
--- Dynamic parameters (may change while driving)
672672
-----------------------------------------------------------------------------------------------------------------------
673-
function AIDriveStrategyFieldWorkCourse:getTurnEndSideOffset()
673+
function AIDriveStrategyFieldWorkCourse:getTurnEndSideOffset(isHeadlandTurn)
674674
return 0
675675
end
676676

scripts/ai/strategies/AIDriveStrategyPlowCourse.lua

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ function AIDriveStrategyPlowCourse:getDriveData(dt, vX, vY, vZ)
5050
--- we still need to check if the rotation
5151
--- is in correct direction, as the course generator directed.
5252
self:rotatePlows()
53-
self:debug("Needs to wait until the plow has finished rotating.")
54-
self.state = self.states.ROTATING_PLOW
55-
else
53+
self:debug("Needs to wait until the plow has finished rotating.")
54+
self.state = self.states.ROTATING_PLOW
55+
else
5656
--- The plow can not be rotated,
5757
--- so we check if the plow is unfolded
5858
--- and try again to rotate the plow in the correct direction.
5959
self:debug("Plows have to be unfolded first!")
60-
self.state = self.states.UNFOLDING_PLOW
61-
end
60+
self.state = self.states.UNFOLDING_PLOW
61+
end
6262
elseif self.state == self.states.ROTATING_PLOW then
6363
self:setMaxSpeed(0)
6464
if not self:isPlowRotating() then
@@ -70,13 +70,13 @@ function AIDriveStrategyPlowCourse:getDriveData(dt, vX, vY, vZ)
7070
end
7171
elseif self.state == self.states.UNFOLDING_PLOW then
7272
self:setMaxSpeed(0)
73-
if self:isPlowRotationAllowed() then
73+
if self:isPlowRotationAllowed() then
7474
--- The Unfolding has finished and
7575
--- we need to check if the rotation is correct.
7676
self:rotatePlows()
7777
self:debug("Plow was unfolded and rotation can begin")
78-
self.state = self.states.ROTATING_PLOW
79-
elseif self:getCanContinueWork() then
78+
self.state = self.states.ROTATING_PLOW
79+
elseif self:getCanContinueWork() then
8080
--- Unfolding has finished and no extra rotation is needed.
8181
self:updatePlowOffset()
8282
self:startWaitingForLower()
@@ -104,7 +104,7 @@ end
104104
--- Updates the X Offset based on the plows attached.
105105
function AIDriveStrategyPlowCourse:updatePlowOffset()
106106
local xOffset = 0
107-
for _, controller in pairs(self.controllers) do
107+
for _, controller in pairs(self.controllers) do
108108
if controller.getAutomaticXOffset then
109109
local autoOffset = controller:getAutomaticXOffset()
110110
if autoOffset == nil then
@@ -127,8 +127,8 @@ end
127127
--- Is a plow currently rotating?
128128
---@return boolean
129129
function AIDriveStrategyPlowCourse:isPlowRotating()
130-
for _, controller in pairs(self.controllers) do
131-
if controller.isRotationActive and controller:isRotationActive() then
130+
for _, controller in pairs(self.controllers) do
131+
if controller.isRotationActive and controller:isRotationActive() then
132132
return true
133133
end
134134
end
@@ -139,8 +139,8 @@ end
139139
---@return boolean
140140
function AIDriveStrategyPlowCourse:isPlowRotationAllowed()
141141
local allowed = true
142-
for _, controller in pairs(self.controllers) do
143-
if controller.getIsPlowRotationAllowed and not controller:getIsPlowRotationAllowed() then
142+
for _, controller in pairs(self.controllers) do
143+
if controller.getIsPlowRotationAllowed and not controller:getIsPlowRotationAllowed() then
144144
allowed = false
145145
end
146146
end
@@ -150,49 +150,48 @@ end
150150
--- Initial plow rotation based on the ridge marker side selection by the course generator.
151151
function AIDriveStrategyPlowCourse:rotatePlows()
152152
self:debug('Starting work: check if plow needs to be turned.')
153-
-- on the headland just check which side was worked last, on the center, check the direction of the next turn
154-
-- as at the first pass both sides are unworked and need some other indication on which side the plow should be
155153
local ix = self.ppc:getCurrentWaypointIx()
156-
local plowShouldBeOnTheLeft
157-
if self.course:isOnHeadland(ix) then
158-
local clockwise = self.course:isOnClockwiseHeadland(ix)
159-
plowShouldBeOnTheLeft = not clockwise
160-
self:debug('On a headland (clockwise %s), plow should be on the left %s', tostring(clockwise), tostring(plowShouldBeOnTheLeft))
161-
else
162-
local isNextTurnLeft = self.course:isNextTurnLeft(ix)
163-
if isNextTurnLeft == nil then
164-
-- don't know if left or right, so just use the last worked side
165-
plowShouldBeOnTheLeft = self.course:isLeftSideWorked(ix)
166-
self:debug('On the center, next turn direction unknown, plow should be on the left %s', tostring(plowShouldBeOnTheLeft))
167-
else
168-
plowShouldBeOnTheLeft = not isNextTurnLeft
169-
self:debug('On the center, plow should be on the left %s', tostring(plowShouldBeOnTheLeft))
154+
local plowShouldBeOnTheLeft = self.course:shouldPlowBeOnTheLeft(ix)
155+
for _, controller in pairs(self.controllers) do
156+
if controller.rotate then
157+
controller:rotate(plowShouldBeOnTheLeft)
170158
end
171159
end
160+
end
161+
162+
--- Do we have at least one plow that can be rotated?
163+
---@return boolean
164+
function AIDriveStrategyPlowCourse:haveRotatablePlow()
172165
for _, controller in pairs(self.controllers) do
173-
if controller.rotate then
174-
controller:rotate(plowShouldBeOnTheLeft)
166+
if controller.isRotatablePlow and controller:isRotatablePlow() then
167+
return true
175168
end
176169
end
170+
return false
177171
end
178172

179173
-----------------------------------------------------------------------------------------------------------------------
180174
--- Dynamic parameters (may change while driving)
181175
-----------------------------------------------------------------------------------------------------------------------
182-
function AIDriveStrategyPlowCourse:getTurnEndSideOffset()
183-
if self:isWorking() then
176+
function AIDriveStrategyPlowCourse:getTurnEndSideOffset(isHeadlandTurn)
177+
-- on headland turns we do not rotate the plow, and since the course already has the offset, nothing to do,
178+
-- the context will calculate with the offset of the course already, no additional side offset is needed
179+
if self:isWorking() and not isHeadlandTurn and self:haveRotatablePlow() then
184180
self:updatePlowOffset()
185-
-- need the double tool offset as the turn end still has the current offset, after the rotation it'll be
186-
-- on the other side, (one toolOffsetX would put it to 0 only)
181+
-- need the double tool offset as the turn end waypoint still has the current offset, but after rotating,
182+
-- the plow will be on the other side, (one toolOffsetX would put it to 0 only)
183+
self:debug('Setting turn end side offset to %.2f for the plow', 2 * self.aiOffsetX)
187184
return 2 * self.aiOffsetX
188185
else
186+
self:debug('No turn end side offset, working: %s, headland turn %s, have rotatable plow %s ',
187+
tostring(self:isWorking()), tostring(isHeadlandTurn), tostring(self:haveRotatablePlow()))
189188
return 0
190189
end
191190
end
192191

193192
function AIDriveStrategyPlowCourse:updateFieldworkOffset(course)
194-
--- Ignore the tool offset setting.
195-
course:setOffset((self.aiOffsetX or 0), (self.aiOffsetZ or 0))
193+
--- Ignore the tool offset setting.
194+
course:setOffset((self.aiOffsetX or 0), (self.aiOffsetZ or 0))
196195
end
197196

198197
--- When we return from a turn, the offset is reverted and should immediately set, not waiting

scripts/ai/turns/AITurn.lua

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,8 +1000,6 @@ function StartRowOnly:getDriveData()
10001000
TurnManeuver.LOWER_IMPLEMENT_AT_TURN_END) then
10011001
self.state = self.states.APPROACHING_ROW
10021002
self:debug('Approaching row')
1003-
self.driveStrategy:raiseControllerEvent(AIDriveStrategyCourse.onTurnEndProgressEvent,
1004-
self:getLowerImplementNode(), self.ppc:isReversing(), true, not self.turnContext:isNextTurnLeft())
10051003
end
10061004
elseif self.state == self.states.APPROACHING_ROW then
10071005
self.workStartHandler:lowerImplementsAsNeeded(self:getLowerImplementNode(), self.ppc:isReversing())

scripts/ai/turns/TurnContext.lua

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ function TurnContext:init(vehicle, course, turnStartIx, turnEndIx, turnNodes, wo
8989

9090
self.dx, _, self.dz = localToLocal(self.turnEndWpNode.node, self.workEndNode, 0, 0, 0)
9191
self.leftTurn = self.dx > 0
92-
self.nextTurnLeft = course:isNextTurnLeft(turnEndIx)
92+
self.cornerOffsetX, _ = course:getOffset()
93+
self.plowShouldBeOnTheLeft = course:shouldPlowBeOnTheLeft(self.turnEndWpIx)
9394
self:debug('start ix = %d, back marker = %.1f, front marker = %.1f',
9495
turnStartIx, self.backMarkerDistance, self.frontMarkerDistance)
9596
end
@@ -238,10 +239,6 @@ function TurnContext:getHeadlandAngle()
238239
return math.abs(CpMathUtil.getDeltaAngle(math.rad(self.turnEndWp.angle), math.rad(self.turnStartWp.angle)))
239240
end
240241

241-
function TurnContext:isNextTurnLeft()
242-
return self.nextTurnLeft
243-
end
244-
245242
function TurnContext:isHeadlandCorner()
246243
-- in headland turns there's no significant direction change at the turn start waypoint, as the turn end waypoint
247244
-- marks the actual corner. In a non-headland turn (usually 180) there is about 90 degrees direction change at
@@ -282,6 +279,11 @@ function TurnContext:isLeftTurn()
282279
end
283280
end
284281

282+
--- Should the plow be rotated to the left after the turn?
283+
function TurnContext:shouldPlowBeOnTheLeft()
284+
return self.plowShouldBeOnTheLeft
285+
end
286+
285287
---@return number offset of the turn end in meters forward (>0) or back (<0)
286288
function TurnContext:getTurnEndForwardOffset()
287289
return self.dz
@@ -363,8 +365,8 @@ function TurnContext:createCorner(vehicle, r)
363365
local endAngleDeg = self:getAverageEndAngleDeg()
364366
CpUtil.debugVehicle(CpDebug.DBG_TURN, vehicle, 'start angle: %.1f, end angle: %.1f (from %.1f and %.1f)', self.beforeTurnStartWp.angle,
365367
endAngleDeg, self.turnEndWp.angle, self.afterTurnEndWp.angle)
366-
return Corner(vehicle, self.beforeTurnStartWp.angle, self.turnStartWp, endAngleDeg, self.turnEndWp, r,
367-
vehicle:getCpSettings().toolOffsetX:getValue())
368+
-- use the course X offset as that's the best indication of the current, actual offset X the vehicle is using
369+
return Corner(vehicle, self.beforeTurnStartWp.angle, self.turnStartWp, endAngleDeg, self.turnEndWp, r, self.cornerOffsetX)
368370
end
369371

370372

0 commit comments

Comments
 (0)