-
-
Notifications
You must be signed in to change notification settings - Fork 69
Expand file tree
/
Copy pathSelfUnloadHelper.lua
More file actions
213 lines (198 loc) · 12 KB
/
SelfUnloadHelper.lua
File metadata and controls
213 lines (198 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
--[[
This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25)
Copyright (C) 2022 Peter Vaiko
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
]]
--- Helper functions for finding a trailer around the field where a combine or an auger wagon can unload
---@class SelfUnloadHelper
SelfUnloadHelper = {}
SelfUnloadHelper.debugChannel = CpDebug.DBG_FIELDWORK
-- keep the helper nodes we create in this table for each vehicle so no nodes need to be created/destroyed
SelfUnloadHelper.bestFillNodes = {}
-- search for trailers within this distance from the field
SelfUnloadHelper.maxDistanceFromField = 20
--- Find a trailer we can use for self unloading
---@param fieldPolygon Polygon the field boundary. We'll look for targets on this field, or close to the boundary.
---@param myVehicle table to find the target closest to myVehicle, for logging, to exclude targets attached to this vehicle
---@param implementWithPipe table trailer or combine with the pipe
---@param pipeOffsetX number pipe side offset (distance from combine centerline, >0 left, <0 right
---@return table|nil trailer object (nil if no trailer found
---@return table|nil best fill node of trailer
---@return number|nil distance of trailer from myVehicle
function SelfUnloadHelper:findBestTrailer(fieldPolygon, myVehicle, implementWithPipe, pipeOffsetX)
if fieldPolygon == nil or #fieldPolygon == 0 then
CpUtil.errorVehicle(myVehicle, 'Field polygon is nil or empty, can\'t find a trailer to unload to')
return nil
end
local bestTrailer, bestFillUnitIndex, bestFillType
local minDistance = math.huge
for _, otherVehicle in pairs(g_currentMission.vehicleSystem.vehicles) do
if SpecializationUtil.hasSpecialization(Trailer, otherVehicle.specializations) then
local rootVehicle = otherVehicle:getRootVehicle()
local attacherVehicle
if SpecializationUtil.hasSpecialization(Attachable, otherVehicle.specializations) then
attacherVehicle = otherVehicle.spec_attachable:getAttacherVehicle()
end
local x, _, z = getWorldTranslation(otherVehicle.rootNode)
-- if the trailer is within 20 m of the field perimeter, we are good
local isOnField, closestDistance = CpMathUtil.isWithinDistanceToPolygon(fieldPolygon, x, z, SelfUnloadHelper.maxDistanceFromField)
if not isOnField then
-- not within 20 m, but could still be on the field
isOnField = CpMathUtil.isPointInPolygon(fieldPolygon, x, z)
end
local lastSpeed = rootVehicle:getLastSpeed()
local isCpActive = rootVehicle.getIsCpActive and rootVehicle:getIsCpActive()
CpUtil.debugVehicle(self.debugChannel, myVehicle,
'%s is a trailer %s on my field, closest distance to the field is %.1f, attached to %s, root vehicle is %s, last speed %.1f, CP active %s',
otherVehicle:getName(), isOnField and '' or 'NOT', closestDistance, attacherVehicle and attacherVehicle:getName() or 'none',
rootVehicle:getName(), lastSpeed, isCpActive)
-- consider only trailer on my field or close to my field, not driven by CP and stopped
if rootVehicle ~= myVehicle and not isCpActive and lastSpeed < 0.1 and isOnField and
not self:isInvalidAutoDriveTarget(myVehicle, rootVehicle) then
local d = calcDistanceFrom(myVehicle:getAIDirectionNode(), otherVehicle.rootNode or otherVehicle.nodeId)
local canLoad, fillUnitIndex, fillType = ImplementUtil.getCanLoadTo(
otherVehicle,
implementWithPipe,
nil,
function(...)
CpUtil.debugVehicle(self.debugChannel, myVehicle, "%s attached to: %s => %s", CpUtil.getName(otherVehicle),
otherVehicle.rootVehicle and CpUtil.getName(otherVehicle.rootVehicle) or "no root vehicle", string.format(...))
end )
if d < minDistance and canLoad then
bestTrailer = otherVehicle
bestFillUnitIndex = fillUnitIndex
bestFillType = fillType
minDistance = d
end
end
end
end
local fillRootNode
if bestTrailer then
fillRootNode = bestTrailer:getFillUnitExactFillRootNode(bestFillUnitIndex)
CpUtil.debugVehicle(self.debugChannel, myVehicle,
'Best trailer is %s at %.1f meters, fill type %s, free capacity %d, fill unit index %s',
bestTrailer:getName(), minDistance, g_fillTypeManager:getFillTypeNameByIndex(bestFillType),
bestTrailer:getFillUnitFreeCapacity(bestFillUnitIndex), bestFillUnitIndex)
local bestFillNode = self:findBestFillNode(myVehicle, fillRootNode, pipeOffsetX)
return bestTrailer, bestFillNode, minDistance
else
CpUtil.infoVehicle(myVehicle, 'Found no trailer to unload to.')
return nil
end
end
function SelfUnloadHelper:findBestFillNode(myVehicle, fillRootNode, offset)
local dx, dy, dz = localToLocal(fillRootNode, AIUtil.getDirectionNode(myVehicle), offset, 0, 0)
local dLeft = MathUtil.vector3Length(dx, dy, dz)
dx, dy, dz = localToLocal(fillRootNode, AIUtil.getDirectionNode(myVehicle), -offset, 0, 0)
local dRight = MathUtil.vector3Length(dx, dy, dz)
CpUtil.debugVehicle(self.debugChannel, myVehicle, 'Trailer left side distance %d, right side %d', dLeft, dRight)
if dLeft <= dRight then
-- left side of the trailer is closer, so turn the fillRootNode around as the combine must approach the
-- trailer from the front of the trailer
if not self.bestFillNodes[myVehicle] then
self.bestFillNodes[myVehicle] = CpUtil.createNode('bestFillNode', 0, 0, math.pi, fillRootNode)
else
unlink(self.bestFillNodes[myVehicle])
link(fillRootNode, self.bestFillNodes[myVehicle])
setRotation(self.bestFillNodes[myVehicle], 0, math.pi, 0)
end
return self.bestFillNodes[myVehicle]
else
-- right side closer, combine approaches the trailer from the rear, driving the same direction as the getFillUnitExactFillRootNode
return fillRootNode
end
end
---@param fieldPolygon Polygon the field boundary. We'll look for targets on this field, or close to the boundary.
---@param myVehicle table to find the target closest to myVehicle, for logging, to exclude targets attached to this vehicle
---@param implementWithPipe table
---@param objectWithPipeAttributes PipeController any object, usually a PipeController which has pipe offset attributes
---@param bestTrailer table|nil optional trailer object to use, will find one if nil
---@param fillRootNode number|nil optional fill node for the trailer, must not be nil if bestTrailer is not nil
function SelfUnloadHelper:getTargetParameters(fieldPolygon, myVehicle, implementWithPipe, objectWithPipeAttributes,
bestTrailer, fillRootNode)
if not bestTrailer then
-- no trailer passed in, let's find one
bestTrailer, fillRootNode = SelfUnloadHelper:findBestTrailer(fieldPolygon,
myVehicle, implementWithPipe,
objectWithPipeAttributes.pipeOffsetX)
if not bestTrailer then
return nil
end
end
local targetNode = fillRootNode or bestTrailer.rootNode
local trailerRootNode = bestTrailer.rootNode
local trailerLength = bestTrailer.size.length
local trailerWidth = bestTrailer.size.width
local _, _, dZ = localToLocal(trailerRootNode, targetNode, 0, 0, 0)
-- this should put the pipe's end 1.6 m from the trailer's edge towards the middle (but not more than half the width
-- of the trailer, so the pipe always stays on the side closer to the harvester. We are not aiming for
-- the centerline of the trailer to avoid bumping into very wide trailers, we don't want to get closer
-- than what is absolutely necessary.
local offsetX = math.max(3.8, math.abs(objectWithPipeAttributes.pipeOffsetX)) + trailerWidth / 2 - math.min(1.6, trailerWidth / 2)
offsetX = objectWithPipeAttributes.pipeOnLeftSide and -offsetX or offsetX
-- arrive near the trailer alignLength meters behind the target, from there, continue straight a bit
local _, steeringLength = AIUtil.getSteeringParameters(myVehicle)
--- Make sure the front marker distance is also checked for large harvesters like the big potato harvester.
local _, frontMarkerOffset = Markers.getFrontMarkerNode(myVehicle)
local alignLength = (trailerLength / 2) + dZ + math.max(myVehicle.size.length / 2 + frontMarkerOffset, steeringLength)
CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, myVehicle,
'Trailer length: %.1f, width: %.1f, dZ: %.1f, align length %.1f, my length: %.1f, steering length %.1f, offsetX %.1f, frontMarkerOffset: %.2f',
trailerLength, trailerWidth, dZ, alignLength,
myVehicle.size.length, steeringLength, offsetX, frontMarkerOffset)
return targetNode, alignLength, offsetX, bestTrailer
end
--- Check if this trailer is driven by AutoDrive and is really ready to be loaded. There may be
--- more than one AD trailer waiting, but we want to make sure we unload into the first one only,
--- the one which is ready to drive away.
function SelfUnloadHelper:isInvalidAutoDriveTarget(myVehicle, otherVehicle)
if otherVehicle and otherVehicle.ad and otherVehicle.ad.stateModule and otherVehicle.ad.stateModule:isActive()
and not otherVehicle.ad.drivePathModule:isTargetReached() then
CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, myVehicle,
'%s is an active AutoDrive vehicle but did not reach its target, ignoring', CpUtil.getName(otherVehicle))
return true
else
return false
end
end
--- Gets fill nodes of all fill units.
---@return table[]|nil Fill root nodes found
---@return number Number of nodes found
function SelfUnloadHelper:getTrailersTargetNodes(vehicleWithTrailers)
local targetNodes = {}
local trailers = AIUtil.getAllChildVehiclesWithSpecialization(vehicleWithTrailers, Trailer)
if not trailers then
CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, vehicleWithTrailers,'Can\'t find any trailers')
return {}, 0
end
for _, trailer in pairs(trailers) do
for ix, _ in pairs(trailer:getFillUnits()) do
local node = trailer:getFillUnitExactFillRootNode(ix)
local _, _, trailerOffset = localToLocal(node, trailer.rootNode, 0, 0, 0)
local _, _, vehicleOffset = localToLocal(vehicleWithTrailers.rootNode, trailer.rootNode, 0, 0, 0)
if node then
table.insert(targetNodes, {
node = node,
fillUnitIx = ix,
trailer = trailer,
trailerOffset = trailerOffset,
vehicleOffset = -vehicleOffset,
})
end
end
end
table.sort(targetNodes, function (a, b)
--- Sorts these nodes to make sure the front most node is the first.
return b.vehicleOffset < a.vehicleOffset or b.vehicleOffset == a.vehicleOffset and b.trailerOffset < a.trailerOffset
end)
return targetNodes, #targetNodes
end