Skip to content

Commit c7212f6

Browse files
authored
Merge pull request #1121 from Courseplay/vehicle-size-scanner
feat: use actual vehicle dimensions for pathfinding
2 parents 435feea + f66359a commit c7212f6

5 files changed

Lines changed: 274 additions & 99 deletions

File tree

modDesc.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ Changelog 8.1.0.0
190190
<sourceFile filename="scripts/ai/util/AIUtil.lua"/>
191191
<sourceFile filename="scripts/ai/util/FillLevelUtil.lua"/>
192192
<sourceFile filename="scripts/ai/util/ImplementUtil.lua"/>
193+
<sourceFile filename="scripts/ai/util/VehicleSizeScanner.lua"/>
193194
<sourceFile filename="scripts/ai/util/WorkWidthUtil.lua"/>
194195
<sourceFile filename="scripts/ai/PathfinderController.lua"/>
195196
<sourceFile filename="scripts/ai/ProximityController.lua"/>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
--[[
2+
This file is part of Courseplay (https://github.com/Courseplay)
3+
Copyright (C) 2025 Courseplay Dev Team
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
]]
18+
19+
--- A utility to scan the actual vehicle size using overlap boxes.
20+
VehicleSizeScanner = CpObject()
21+
22+
function VehicleSizeScanner:init()
23+
self.logger = Logger('VehicleSizeScanner', Logger.level.debug, CpDebug.DBG_IMPLEMENTS)
24+
end
25+
26+
--- Scan the size of the given vehicle, using the given reference node (or the root node if nil).
27+
--- This function uses overlap boxes moved towards the vehicle from all four directions to find the extents
28+
--- by detecting collisions between the box and the vehicle.
29+
---@param vehicle Vehicle
30+
---@param referenceNode number|nil
31+
---@return number front distance of the frontmost point from the reference node
32+
---@return number rear distance of the rearmost point from the reference node
33+
---@return number left distance of the leftmost point from the reference node
34+
---@return number right distance of the rightmost point from the reference node
35+
function VehicleSizeScanner:scan(vehicle, referenceNode)
36+
self.left = self:_measureDimension(vehicle, referenceNode, 50, 0.1, 'x')
37+
self.right = self:_measureDimension(vehicle, referenceNode, -50, -0.1, 'x')
38+
self.front = self:_measureDimension(vehicle, referenceNode, 50, 0.1, 'z')
39+
self.rear = self:_measureDimension(vehicle, referenceNode, -50, -0.1, 'z')
40+
self.logger:debug(vehicle, 'Front: %.1f Rear: %.1f Left: %.1f Right: %.1f ',
41+
self.front, self.rear, self.left, self.right)
42+
return self.front, self.rear, self.left, self.right
43+
end
44+
45+
function VehicleSizeScanner:getLength()
46+
-- front is positive z, rear is negative z
47+
return self.front - self.rear
48+
end
49+
50+
function VehicleSizeScanner:getWidth()
51+
-- left is positive x, right is negative x
52+
return self.left - self.right
53+
end
54+
55+
function VehicleSizeScanner:_measureDimension(vehicle, referenceNode, startDistance, endDistance, axis)
56+
local step = endDistance > startDistance and 0.1 or -0.1
57+
local boxHalfSize = 25
58+
local obX, obY, obZ, getLocalPosition
59+
if axis == 'x' then
60+
getLocalPosition = function(node, d)
61+
return localToWorld(node, d, 0, 0)
62+
end
63+
-- a rectangle (thin box) in the y-z plane, moving along the x axis
64+
obX, obY, obZ = 0.1, 10, boxHalfSize
65+
elseif axis == 'y' then
66+
getLocalPosition = function(node, d)
67+
return localToWorld(node, 0, d, 0)
68+
end
69+
-- a rectangle (thin box) in the x-z plane, moving along the y axis
70+
obX, obY, obZ = boxHalfSize, 0.1, boxHalfSize
71+
elseif axis == 'z' then
72+
getLocalPosition = function(node, d)
73+
return localToWorld(node, 0, 0, d)
74+
end
75+
-- a rectangle (thin box) in the x-y plane, moving along the z axis
76+
obX, obY, obZ = boxHalfSize, 10, 0.1
77+
end
78+
local dimension = 3
79+
local node = referenceNode or vehicle.rootNode
80+
local xRot, yRot, zRot = getWorldRotation(node)
81+
self.vehicleBeingScanned = vehicle
82+
self.scannedVehicleFound = false
83+
-- create an overlap box, very thin, like a plane and move it towards the vehicle until we find a collision
84+
for ix = startDistance, endDistance, step do
85+
local x, y, z = getLocalPosition(vehicle.rootNode, ix)
86+
overlapBox(x, y, z, xRot, yRot, zRot, obX, obY, obZ, "_overlapBoxCallback", self, CpUtil.getDefaultCollisionFlags(), true, true, true, true)
87+
if self.scannedVehicleFound then
88+
--PathfinderUtil.addOverlapBox(x, y, z, xRot, yRot, zRot, obX, obY, obZ)
89+
dimension = ix
90+
break
91+
end
92+
end
93+
return dimension
94+
end
95+
96+
function VehicleSizeScanner:_overlapBoxCallback(transformId)
97+
local collidingObject = g_currentMission.nodeToObject[transformId]
98+
if collidingObject and self.vehicleBeingScanned == collidingObject then
99+
self.scannedVehicleFound = true
100+
end
101+
end
102+

0 commit comments

Comments
 (0)