This repository has been archived by the owner on May 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 525
/
ProximitySensor.lua
318 lines (280 loc) · 12.7 KB
/
ProximitySensor.lua
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
--[[
This file is part of Courseplay (https://github.com/Courseplay/courseplay)
Copyright (C) 2020 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/>.
]]--
---@class ProximitySensor
ProximitySensor = CpObject()
--- No matter what angle, don't make it extend to left/right more than this
ProximitySensor.maxSideExtension = 4
function ProximitySensor:init(node, yRotationDeg, range, height, xOffset)
self.node = node
self.xOffset = xOffset
self.yRotation = math.rad(yRotationDeg)
self.lx, self.lz = MathUtil.getDirectionFromYRotation(self.yRotation)
self.range = math.min(range, ProximitySensor.maxSideExtension / math.cos((math.pi / 2 - math.abs(self.yRotation))))
self.dx, self.dz = self.lx * self.range, self.lz * self.range
self.height = height or 0
self.lastUpdateLoopIndex = 0
self.enabled = true
-- vehicles can only be ignored temporarily
self.ignoredVehicle = CpTemporaryObject()
end
function ProximitySensor:enable()
self.enabled = true
end
function ProximitySensor:disable()
self.enabled = false
end
---@param vehicle table vehicle to ignore
---@param ttlMs number milliseconds to ignore this vehicle. After ttlMs ms it won't be ignored.
function ProximitySensor:setIgnoredVehicle(vehicle, ttlMs)
self.ignoredVehicle:set(vehicle, ttlMs)
end
function ProximitySensor:update()
-- already updated in this loop, no need to raycast again
if g_updateLoopIndex == self.lastUpdateLoopIndex then return end
self.lastUpdateLoopIndex = g_updateLoopIndex
local x, y, z = localToWorld(self.node, self.xOffset, 0, 0)
-- get the terrain height at the end of the raycast line
local tx, _, tz = localToWorld(self.node, self.dx, 0, self.dz)
local y2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, tx, 0, tz)
-- make sure the raycast line is parallel with the ground
local ny = (y2 - y) / self.range
local nx, _, nz = localDirectionToWorld(self.node, self.lx, 0, self.lz)
self.distanceOfClosestObject = math.huge
self.objectId = nil
if self.enabled then
raycastClosest(x, y + self.height, z, nx, ny, nz, 'raycastCallback', self.range, self,
0x1000 + 0x100 + 0x80 + 0x40 + 0x20)
--bitOR(AIVehicleUtil.COLLISION_MASK, 0x1f0))
--cpDebug:drawLine(x, y + self.height, z, 0, 1, 0, x + 5 * nx, y + self.height + 5 * ny, z + 5 * nz)
end
if courseplay.debugChannels[courseplay.DBG_TRAFFIC] and self.distanceOfClosestObject <= self.range then
local green = self.distanceOfClosestObject / self.range
local red = 1 - green
cpDebug:drawLine(x, y + self.height, z, red, green, 0, self.closestObjectX, self.closestObjectY, self.closestObjectZ)
end
end
function ProximitySensor:raycastCallback(objectId, x, y, z, distance)
local object = g_currentMission:getNodeObject(objectId)
if object and object.getRootVehicle and object:getRootVehicle() == self.ignoredVehicle:get() then
-- ignore this vehicle
return
end
self.distanceOfClosestObject = distance
self.objectId = objectId
self.closestObjectX, self.closestObjectY, self.closestObjectZ = x, y, z
end
function ProximitySensor:getClosestObjectDistance()
--self:showDebugInfo()
return self.distanceOfClosestObject
end
function ProximitySensor:getClosestObject()
return g_currentMission:getNodeObject(self.objectId)
end
function ProximitySensor:getClosestRootVehicle()
if self.objectId then
local object = g_currentMission:getNodeObject(self.objectId)
if object and object.getRootVehicle then
return object:getRootVehicle()
end
end
end
function ProximitySensor:showDebugInfo()
if not courseplay.debugChannels[courseplay.DBG_TRAFFIC] then return end
local text = string.format('%.1f ', self.distanceOfClosestObject)
if self.objectId then
local object = g_currentMission:getNodeObject(self.objectId)
if object then
if object.getRootVehicle then
text = text .. 'vehicle ' .. object:getName()
else
text = text .. object:getName()
end
else
for key, classId in pairs(ClassIds) do
if getHasClassId(self.objectId, classId) then
text = text .. ' ' .. key
end
end
end
end
renderText(0.6, 0.4 + self.yRotation / 10, 0.018, text .. string.format(' %d', math.deg(self.yRotation)))
end
---@class ProximitySensorPack
ProximitySensorPack = CpObject()
-- maximum angle we rotate the sensor pack into the direction the vehicle is turning
ProximitySensorPack.maxRotation = math.rad(30)
---@param name string a name for this sensor, when multiple sensors are attached to the same node, they need
--- a unique name
---@param vehicle table vehicle we attach the sensor to, used only to rotate the sensor with the steering angle
---@param ppc PurePursuitController PPC of the vehicle, used only to rotate the sensor towards the goal point
---@param node number node (front or back) to attach the sensor to
---@param range number range of the sensor in meters
---@param height number height relative to the node in meters
---@param directionsDeg table of numbers, list of angles in degrees to emit a ray to find objects, 0 is forward, >0 left, <0 right
---@param xOffsets table of numbers, left/right offset of the corresponding sensor in meters, left > 0, right < 0
function ProximitySensorPack:init(name, vehicle, ppc, node, range, height, directionsDeg, xOffsets)
---@type ProximitySensor[]
self.sensors = {}
self.vehicle = vehicle
self.ppc = ppc
self.range = range
self.name = name
self.node = getChild(node, name)
if self.node <= 0 then
-- node with this name does not yet exist
-- add a separate node for the proximity sensor (so we can rotate it independently from 'node'
self.node = courseplay.createNode(name, 0, 0, 0, node)
end
-- reset it on the parent node
setTranslation(self.node, 0, 0, 0)
setRotation(self.node, 0, 0, 0)
self.directionsDeg = directionsDeg
self.xOffsets = xOffsets
self.rotateToGoalPoint = false
self.rotation = 0
for i, deg in ipairs(self.directionsDeg) do
self.sensors[deg] = ProximitySensor(self.node, deg, self.range, height, xOffsets[i] or 0)
end
end
function ProximitySensorPack:debug(...)
courseplay.debugVehicle(courseplay.DBG_TRAFFIC, self.vehicle, ...)
end
function ProximitySensorPack:adjustForwardPosition()
-- are we looking forward
local forward = 1
-- if a sensor about in the middle is pointing back, we are looking back
if math.abs(self.directionsDeg[math.floor(#self.directionsDeg / 2)]) > 90 then
forward = -1
end
local x, y, z = getTranslation(self.node)
self:debug('moving proximity sensor %s %.1f so it does not interfere with own vehicle', self.name, forward * 0.1)
-- move pack forward/back a bit
setTranslation(self.node, x, y, z + forward * 0.1)
end
function ProximitySensorPack:getRange()
return self.range
end
function ProximitySensorPack:callForAllSensors(func, ...)
for _, deg in ipairs(self.directionsDeg) do
func(self.sensors[deg], ...)
end
end
function ProximitySensorPack:disableRotateToGoalPoint()
self.rotateToGoalPoint = false
end
function ProximitySensorPack:update()
if self.rotateToGoalPoint then
-- rotate the entire pack in the direction of the goal point
local dx, dz = self.ppc:getGoalPointDirection()
local yRot = MathUtil.getYRotationFromDirection(dx, dz)
self.rotation = MathUtil.clamp(yRot, -ProximitySensorPack.maxRotation, ProximitySensorPack.maxRotation)
setRotation(self.node, 0, self.rotation, 0)
end
self:callForAllSensors(ProximitySensor.update)
-- show the position of the pack
if courseplay.debugChannels[courseplay.DBG_TRAFFIC] then
local x, y, z = getWorldTranslation(self.node)
local x1, y1, z1 = localToWorld(self.node, 0, 0, 0.5)
cpDebug:drawLine(x, y, z, 0, 0, 1, x, y + 3, z)
cpDebug:drawLine(x, y + 1, z, 0, 1, 0, x1, y1 + 1, z1)
end
end
function ProximitySensorPack:enable()
self:callForAllSensors(ProximitySensor.enable)
end
function ProximitySensorPack:disable()
self:callForAllSensors(ProximitySensor.disable)
end
function ProximitySensorPack:setIgnoredVehicle(vehicle, ttlMs)
self:callForAllSensors(ProximitySensor.setIgnoredVehicle, vehicle, ttlMs)
end
--- @return number, table, number distance of closest object in meters, root vehicle of the closest object,
--- the closest object, average direction of the obstacle in degrees, > 0 right, < 0 left
function ProximitySensorPack:getClosestObjectDistanceAndRootVehicle(deg)
-- make sure we have the latest info, the sensors will make sure they only raycast once per loop
self:update()
if deg and self.sensors[deg] then
return self.sensors[deg]:getClosestObjectDistance(), self.sensors[deg]:getClosestRootVehicle(), deg
else
local closestDistance = math.huge
local closestRootVehicle, closestObject
-- weighted average over the different direction, weight depends on how close the closest object is
local totalWeight, totalDegs, totalDistance = 0, 0, 0
for _, deg in ipairs(self.directionsDeg) do
local d = self.sensors[deg]:getClosestObjectDistance()
if d < self.range then
local weight = (self.range - d) / self.range
totalWeight = totalWeight + weight
-- the direction should be in the tractor's system, therefore we need to compensate here with the
-- current rotation of the pack
totalDegs = totalDegs + weight * (deg + math.deg(self.rotation))
totalDistance = totalDistance + weight * d
end
if d < closestDistance then
closestDistance = d
closestRootVehicle = self.sensors[deg]:getClosestRootVehicle()
closestObject = self.sensors[deg]:getClosestObject()
end
end
if closestRootVehicle == self.vehicle then
self:adjustForwardPosition()
end
return closestDistance, closestRootVehicle, closestObject, totalDegs / totalWeight, totalDistance / totalWeight
end
return math.huge, nil, deg
end
function ProximitySensorPack:disableRightSide()
for _, deg in ipairs(self.directionsDeg) do
if deg <= 0 then
self.sensors[deg]:disable()
end
end
end
function ProximitySensorPack:enableRightSide()
for _, deg in ipairs(self.directionsDeg) do
if deg <= 0 then
self.sensors[deg]:enable()
end
end
end
---@class ForwardLookingProximitySensorPack : ProximitySensorPack
ForwardLookingProximitySensorPack = CpObject(ProximitySensorPack)
--- Pack looking forward, all sensors are in the middle of the vehicle
function ForwardLookingProximitySensorPack:init(vehicle, ppc, node, range, height)
ProximitySensorPack.init(self, 'forward', vehicle, ppc, node, range, height,
{0, 15, 30, 60, 80, -15, -30, -60, -80},
{0, 0, 0, 0, 0, 0, 0, 0, 0})
end
---@class WideForwardLookingProximitySensorPack : ProximitySensorPack
WideForwardLookingProximitySensorPack = CpObject(ProximitySensorPack)
--- Pack looking forward, but sensors distributed evenly through the width of the vehicle
function WideForwardLookingProximitySensorPack:init(vehicle, ppc, node, range, height, width)
local directionsDeg = {80, 60, 30, 15, 0, -15, -30, -60, -80}
local xOffsets = {}
-- spread them out evenly across the width
local dx = width / #directionsDeg
for xOffset = width / 2 - dx / 2, - width / 2 + dx / 2 - 0.1, - dx do
table.insert(xOffsets, xOffset)
end
ProximitySensorPack.init(self, 'wideForward', vehicle, ppc, node, range, height,
directionsDeg, xOffsets)
end
---@class BackwardLookingProximitySensorPack : ProximitySensorPack
BackwardLookingProximitySensorPack = CpObject(ProximitySensorPack)
function BackwardLookingProximitySensorPack:init(vehicle, ppc, node, range, height)
ProximitySensorPack.init(self, 'backward', vehicle, ppc, node, range, height,
{120, 150, 180, -150, -120},
{0, 0, 0, 0, 0})
end