Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pocket #3584

Merged
merged 9 commits into from
Nov 6, 2024
Merged

Pocket #3584

Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modDesc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ Changelog 7.1.0.0:
<sourceFile filename="scripts/util/CpMathUtil.lua"/>
<sourceFile filename="scripts/util/MovingAverage.lua"/>
<sourceFile filename="scripts/util/CpRemainingTime.lua"/>
<sourceFile filename="scripts/util/HelperNode.lua"/>

<sourceFile filename="scripts/geometry/Vector.lua"/>
<sourceFile filename="scripts/geometry/Vertex.lua"/>
Expand Down
45 changes: 27 additions & 18 deletions scripts/ai/AIDriveStrategyCombineCourse.lua
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function AIDriveStrategyCombineCourse:init(task, job)
self.unloaderRequestedToIgnoreProximity = CpTemporaryObject()
-- we want to keep to pipe open, even if there is no trailer under it
self.forcePipeOpen = CpTemporaryObject()

self.pocketHelperNode = HelperTerrainNode('pocketHelperNode')
--- Register info texts
self:registerInfoTextForStates(self:getFillLevelInfoText(), {
states = {
Expand All @@ -107,6 +107,11 @@ function AIDriveStrategyCombineCourse:init(task, job)
})
end

function AIDriveStrategyCombineCourse:delete()
self.pocketHelperNode:destroy()
AIDriveStrategyFieldWorkCourse.delete(self)
end

function AIDriveStrategyCombineCourse:getStateAsString()
local s = self.state.name
if self.state == self.states.UNLOADING_ON_FIELD then
Expand Down Expand Up @@ -142,7 +147,7 @@ function AIDriveStrategyCombineCourse:setAllStaticParameters()
self.pullBackDistanceEnd = self.pullBackDistanceStart + 5
-- when making a pocket, how far to back up before changing to forward
-- for very long vehicles, like potato/sugar beet harvesters the 20 meters may not be enough
self.pocketReverseDistance = math.max(1.7 * AIUtil.getVehicleAndImplementsTotalLength(self.vehicle), 32)
self.pocketReverseDistance = AIUtil.getVehicleAndImplementsTotalLength(self.vehicle) * 2.2
-- register ourselves at our boss
-- TODO_22 g_combineUnloadManager:addCombineToList(self.vehicle, self)
self.waitingForUnloaderAtEndOfRow = CpTemporaryObject()
Expand Down Expand Up @@ -322,6 +327,13 @@ function AIDriveStrategyCombineCourse:driveUnloadOnField()
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.unloadState == self.states.MAKING_POCKET then
self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue())
local _, _, dz = self.pocketHelperNode:localToLocal(self.vehicle:getAIDirectionNode(), 0, 0,
self.pipeController:getPipeOffsetZ())
if dz > -18 then
-- we are close enough to the reference waypoint, so stop making the pocket and wait for unload.
self:debug('Waiting for unload in the pocket')
self.unloadState = self.states.WAITING_FOR_UNLOAD_IN_POCKET
end
elseif self.unloadState == self.states.RETURNING_FROM_PULL_BACK then
self:setMaxSpeed(self.settings.turnSpeed:getValue())
elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or
Expand Down Expand Up @@ -500,17 +512,6 @@ function AIDriveStrategyCombineCourse:onWaypointPassed(ix, course)
end
end

if self.state == self.states.UNLOADING_ON_FIELD and
self.unloadState == self.states.MAKING_POCKET and
self.unloadInPocketReferenceIx then
local _, _, dz = self.course:getWaypointLocalPosition(self.vehicle:getAIDirectionNode(), self.unloadInPocketReferenceIx)
if dz < 15 then
-- we are close enough to the reference waypoint, so stop making the pocket and wait for unload.
self:debug('Waiting for unload in the pocket')
self.unloadState = self.states.WAITING_FOR_UNLOAD_IN_POCKET
end
end

if self.returnedFromPocketIx and self.returnedFromPocketIx == ix then
-- back to normal look ahead distance for PPC, no tight turns are needed anymore
self:debug('Reset PPC to normal lookahead distance')
Expand All @@ -532,7 +533,7 @@ function AIDriveStrategyCombineCourse:onLastWaypointPassed()
self:debug('Back from self unload, returning to fieldwork')
self.workStarter:onLastWaypoint()
elseif self.unloadState == self.states.REVERSING_TO_MAKE_A_POCKET then
self:debug('Reversed, now start making a pocket to waypoint %d', self.unloadInPocketReferenceIx)
self:debug('Reversed, now start making a pocket')
self:lowerImplements()
self.state = self.states.UNLOADING_ON_FIELD
self.unloadState = self.states.MAKING_POCKET
Expand Down Expand Up @@ -610,6 +611,9 @@ function AIDriveStrategyCombineCourse:changeToUnloadOnField()
self:debug('No room to the left, making a pocket for unload')
self.state = self.states.UNLOADING_ON_FIELD
self.unloadState = self.states.REVERSING_TO_MAKE_A_POCKET
-- place a marker at the current pipe position, we'll use this to find out where to stop
-- making the pocket
self.pocketHelperNode:placeAtNode(Markers.getFrontMarkerNode(self.vehicle), 1, 0, 0, 0)
self:rememberCourse(self.course, nextIx)
-- raise header for reversing
self:raiseImplements()
Expand Down Expand Up @@ -1261,8 +1265,6 @@ function AIDriveStrategyCombineCourse:createPocketCourse()
if not backIx then
return nil
end
-- this is where we'll stop in the pocket for unload
self.unloadInPocketReferenceIx = startIx
-- this where we are back on track after returning from the pocket
self.returnedFromPocketIx = self.ppc:getCurrentWaypointIx()
self:debug('Backing up %.1f meters from waypoint %d to %d to make a pocket', self.pocketReverseDistance, startIx, backIx)
Expand Down Expand Up @@ -1293,8 +1295,8 @@ function AIDriveStrategyCombineCourse:isFuelSaveAllowed()
if self.combine:getIsThreshingDuringRain() then
return true
end
return self:isWaitingForUnload() or self:isChopperWaitingForUnloader()
or AIDriveStrategyCourse.isFuelSaveAllowed(self)
return self:isWaitingForUnload() or self:isChopperWaitingForUnloader()
or AIDriveStrategyCourse.isFuelSaveAllowed(self)
end

--- Check if the vehicle should stop during a turn for example while it
Expand Down Expand Up @@ -2049,6 +2051,13 @@ function AIDriveStrategyCombineCourse:onDraw()
end
end

if CpDebug:isChannelActive(CpDebug.DBG_FIELDWORK, self.vehicle) then
if self.state == self.states.UNLOADING_ON_FIELD and
(self.unloadState == self.states.REVERSING_TO_MAKE_A_POCKET or self.unloadState == self.states.MAKING_POCKET or
self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET) then
self.pocketHelperNode:draw()
end
end
end

--- Don't slow down when discharging. This is a workaround for unloaders getting into the proximity
Expand Down
110 changes: 110 additions & 0 deletions scripts/util/HelperNode.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
--[[
This file is part of Courseplay (https://github.com/Courseplay/FS22_Courseplay)
Copyright (C) 2024 Courseplay Dev Team

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/>.

]]--

-- A helper node is a temporary node to help with relative vehicle position calculations.
-- It wraps the creation of the Giant's engine node.
-- NOTE: it must be destroyed explicitly to avoid node leak.

---@class HelperNode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fantastic idea :)
Maybe you got the motivation to add missing commanly used functions, like worldToLocal(), localDirectionToWorld().

HelperNode = CpObject()

--- Create a new helper node, linking it to the given rootNode.
---@param name string
---@param rootNode number|nil
function HelperNode:init(name, rootNode)
self.node = createTransformGroup(name)
self.rootNode = rootNode
link(self.rootNode, self.node)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens here if rootNode is nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

end

--- Safely destroy a node
function HelperNode:destroy()
if self.node and entityExists(self.node) then
unlink(self.node)
delete(self.node)
end
end

--- Place the node at the given position and rotation.
---@param x number
---@param y number if the node is linked to the terrain, this is relative to the terrain height at x, z
---@param z number
---@param yRotation number|nil Rotation set only if not nil
function HelperNode:place(x, y, z, yRotation)
setTranslation(self.node, x, y, z)
if yRotation then
setRotation(self.node, 0, yRotation, 0)
end
end

---@param text string|nil
function HelperNode:draw(text)
if entityExists(self.node) then
DebugUtil.drawDebugNode(self.node, text or getName(self.node), false, 0)
end
end

-- A helper node that is linked to the terrain root node, and the y coordinate is always
-- the terrain height at the x, z position.
---@class HelperTerrainNode : HelperNode
HelperTerrainNode = CpObject(HelperNode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if inheritence is the best approach here.
I would prefer something like this:

-- Creates a new helper node linked to the terrain root node.
function HelperNode.createTerrainNode(name )
  return HelperNode(name, g_currentMission.terrainRootNode)
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had something similar first, the reason I don't like it, is that the behavior/interpretation for the y coordinate is different.


--- Create a new terrain helper node, linking it to the terrain root node.
---@param name string
function HelperTerrainNode:init(name)
HelperNode.init(self, name, g_currentMission.terrainRootNode)
end

--- Place the node at the given position and rotation.
---@param x number
---@param y number if the node is linked to the terrain, this is relative to the terrain height at x, z
---@param z number
---@param yRotation number|nil Rotation set only if not nil
function HelperTerrainNode:place(x, y, z, yRotation)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer this functionality in a separate function like so maybe:

---@param yRotation number|nil
---@param yOffset number|nil
function HelperNode:placeTerrain(x, z, yRotation, yOffset)

end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was struggling with this as well, probably we don't even need a generic HelperNode (at least as of now, I'm not aware of a use case), and should only implement a HelperTerrainNode.

setTranslation(self.node, x, getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + y, z)
if yRotation then
-- leave z/x rotation as is
local xRotation, _, zRotation = getWorldRotation(node)
setRotation(self.node, xRotation, yRotation, zRotation)
end
end

--- Place the node at the same world position and rotation as the given node.
--- An optional local position can be given to offset the helper node from the given node.
---@param node number
---@param y number|nil relative height to terrain
---@param lx number|nil x coordinate of the point relative to node
---@param ly number|nil y coordinate of the point relative to node
---@param lz number|nil z coordinate of the point relative to node
function HelperTerrainNode:placeAtNode(node, y, lx, ly, lz)
local x, _, z = localToWorld(node, lx or 0, ly or 0, lz or 0)
local xRotation, yRotation, zRotation = getWorldRotation(node)
setRotation(self.node, xRotation, yRotation, zRotation)
self:place(x, y or 0, z)
end

--- Get the position of a point relative to node, in the helper node's coordinate system.
---@param node number
---@param lx number x coordinate of the point relative to the node
---@param ly number y coordinate of the point relative to the node
---@param lz number z coordinate of the point relative to the node
---@return number, number, number
function HelperTerrainNode:localToLocal(node, lx, ly, lz)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is only working for HelperTerrainNode and not for the base HelperNode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

return localToLocal(node, self.node, lx, ly, lz)
end
Loading