LUADOC - Farming Simulator 22

Script v1_7_1_0

Engine v1_7_1_0

Foundation Reference

AITurnStrategy

Description
Base class for a turn strategy Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.
Functions

addNoFullCoverageSegment

Description
Definition
addNoFullCoverageSegment()
Code
1179function AITurnStrategy:addNoFullCoverageSegment(turnSegments)
1180 local offset = self:getNoFullCoverageZOffset()
1181 if offset ~= 0 then
1182 local segment = {}
1183 segment.isCurve = false
1184 segment.moveForward = false
1185 segment.slowDown = true
1186 segment.startPoint = self:getVehicleToWorld(0, 0, -offset, true)
1187 segment.endPoint = self:getVehicleToWorld(0, 0, 0, true)
1188 table.insert(turnSegments, segment)
1189 end
1190end

adjustHeightOfTurningSizeBox

Description
Definition
adjustHeightOfTurningSizeBox()
Code
1125function AITurnStrategy:adjustHeightOfTurningSizeBox(box)
1126 local yMax, yMin = -math.huge, math.huge
1127 for i=1, self.numHeightChecks do
1128 local check = self.heightChecks[i]
1129 local x, _, z = localToWorld(self.vehicleDirectionNode, box.center[1] + box.size[1] * check[1], 0, box.center[3] + box.size[3] * check[2])
1130 local h = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z)
1131 yMax = math.max(yMax, h)
1132 yMin = math.min(yMin, h)
1133 end
1134
1135 local height = math.max(6, (yMax - yMin) + 2)
1136
1137 box.size[2] = height
1138 box.center[2] = 0
1139end

calculatePathPrediction

Description
Fill the given positions table with the predicted turn path based on the reference node
Definition
calculatePathPrediction()
Code
1386function AITurnStrategy:calculatePathPrediction(positions, referenceNode, originX, originY, originZ, posIndex, pointToPointDistance, remainingDistance, remainingPoints)
1387 local activeSegmentIndex, activeSegmentProgress = self:getActiveTurnSegmentByNode(referenceNode, originX, originY, originZ)
1388
1389 if activeSegmentIndex ~= nil then
1390 self:createPointsForSegment(activeSegmentIndex, activeSegmentProgress, positions, posIndex, pointToPointDistance, remainingDistance, remainingPoints)
1391 end
1392
1393 return activeSegmentIndex ~= nil
1394end

checkCollisionInFront

Description
Definition
checkCollisionInFront()
Code
1102function AITurnStrategy:checkCollisionInFront(turnData, lookAheadDistance)
1103 lookAheadDistance = lookAheadDistance or 5
1104
1105 local maxX = turnData.sideOffsetLeft
1106 local minX = turnData.sideOffsetRight
1107 local maxZ = math.max(4, turnData.toolOverhang.front.zt)
1108
1109 local box = {name="checkCollisionInFront"}
1110 box.center = {maxX - (maxX-minX)/2, 0, maxZ/2 + lookAheadDistance/2}
1111 box.rotation = {0,0,0}
1112 box.size = {(maxX-minX)/2, 5, maxZ/2 + lookAheadDistance/2}
1113
1114 self.collisionHit = self:getIsBoxColliding(box)
1115
1116 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
1117 table.insert(self.maxTurningSizeBoxes, box)
1118 end
1119
1120 return self.collisionHit
1121end

clearTurnSegments

Description
Definition
clearTurnSegments()
Code
719function AITurnStrategy:clearTurnSegments()
720 -- clear turn segments
721 for i=#self.turnSegments, 1, -1 do
722 local segment = table.remove(self.turnSegments, i)
723 if segment ~= nil and segment.o ~= nil then
724 delete(segment.o)
725 end
726 end
727end

collisionTestCallback

Description
Definition
collisionTestCallback()
Code
1024function AITurnStrategy:collisionTestCallback(transformId)
1025 -- ai should not collide with vehicles and objects, only with the dynamic traffic
1026 local object = g_currentMission:getNodeObject(transformId)
1027 if object == nil or object:isa(Placeable) then
1028 self.collisionHit = true
1029 return false
1030 end
1031end

createPointsForSegment

Description
Create path points starting from this driving segment at the given progress position based on remaining distance
Definition
createPointsForSegment()
Code
1295function AITurnStrategy:createPointsForSegment(segmentIndex, segmentProgress, positions, posIndex, pointToPointDistance, remainingDistance, remainingPoints)
1296 local segment = self.turnSegments[segmentIndex]
1297 local curAngle = 0
1298 if segment.isCurve then
1299 curAngle = segmentProgress * (segment.endAngle-segment.startAngle) + segment.startAngle
1300 local segmentCircleLength = 2 * math.pi * segment.radius
1301
1302 while remainingPoints > 0 and remainingDistance > 0 do
1303 local nextAngle = curAngle + (pointToPointDistance / segmentCircleLength * (2 * math.pi)) * MathUtil.sign(segment.endAngle-curAngle)
1304 if segment.endAngle > segment.startAngle then
1305 if nextAngle >= segment.endAngle or nextAngle <= segment.startAngle then
1306 break
1307 end
1308 else
1309 if nextAngle <= segment.endAngle or nextAngle >= segment.startAngle then
1310 break
1311 end
1312 end
1313
1314 local x1 = segment.radius * math.cos(nextAngle)
1315 local z1 = segment.radius * math.sin(nextAngle)
1316 positions[posIndex+1], positions[posIndex+2], positions[posIndex+3] = localToWorld(segment.o, x1, 0, z1)
1317 positions[posIndex+2] = positions[2]
1318 posIndex = posIndex + 3
1319 remainingPoints = remainingPoints - 1
1320 remainingDistance = remainingDistance - pointToPointDistance
1321
1322 curAngle = nextAngle
1323 end
1324 else
1325 local segmentLength = MathUtil.vector2Length(segment.startPoint[1]-segment.endPoint[1], segment.startPoint[3]-segment.endPoint[3])
1326 local curAlpha = segmentProgress
1327 while remainingPoints > 0 and remainingDistance > 0 do
1328 local nextAlpha = curAlpha + pointToPointDistance / segmentLength
1329 if nextAlpha < 0 or nextAlpha > 1 then
1330 break
1331 end
1332
1333 local x1, _, z1 = MathUtil.vector3ArrayLerp(segment.startPoint, segment.endPoint, nextAlpha)
1334 positions[posIndex+1], positions[posIndex+2], positions[posIndex+3] = x1, positions[2], z1
1335 posIndex = posIndex + 3
1336 remainingPoints = remainingPoints - 1
1337 remainingDistance = remainingDistance - pointToPointDistance
1338
1339 curAlpha = nextAlpha
1340 end
1341 end
1342
1343 if remainingDistance > 0 then
1344 segmentIndex = segmentIndex + 1
1345 if self.turnSegments[segmentIndex] ~= nil and self.turnSegments[segmentIndex].moveForward then
1346 self:createPointsForSegment(segmentIndex, 0, positions, posIndex, pointToPointDistance, remainingDistance, remainingPoints)
1347 else
1348 if posIndex >= 3 then
1349 local dirX, dirY, dirZ
1350 if segment.isCurve then
1351 local dir = MathUtil.sign(segment.endAngle - segment.startAngle)
1352
1353 if dir ~= 0 then
1354 local preAngle = curAngle + math.rad(10) * -dir
1355 local nextAngle = curAngle + math.rad(10) * dir
1356
1357 local x1, y1, z1 = localToWorld(segment.o, segment.radius * math.cos(preAngle), 0, segment.radius * math.sin(preAngle))
1358 local x2, y2, z2 = localToWorld(segment.o, segment.radius * math.cos(nextAngle), 0, segment.radius * math.sin(nextAngle))
1359 dirX, dirY, dirZ = MathUtil.vector3Normalize(x1-x2, y1-y2, z1-z2)
1360 else
1361 -- start and end angle can be the same depending on the work width
1362 -- in this case the segment has a length of 0, so we can skip it
1363 -- we fill up the other points into the same direction as the previous ones
1364 local x1, y1, z1 = positions[posIndex-5], positions[posIndex-4], positions[posIndex-3]
1365 local x2, y2, z2 = positions[posIndex-2], positions[posIndex-1], positions[posIndex-0]
1366 dirX, dirY, dirZ = MathUtil.vector3Normalize(x1-x2, y1-y2, z1-z2)
1367 end
1368 else
1369 dirX, dirY, dirZ = MathUtil.vector3Normalize(segment.startPoint[1]-segment.endPoint[1], 0, segment.startPoint[3]-segment.endPoint[3])
1370 end
1371
1372 local lx1, ly1, lz1 = positions[posIndex-2], positions[posIndex-1], positions[posIndex-0]
1373 local distance = 0
1374 for i=0, remainingPoints-1 do
1375 distance = distance + pointToPointDistance
1376 positions[posIndex+1], positions[posIndex+2], positions[posIndex+3] = lx1 - dirX * distance, ly1 - dirY * distance, lz1 - dirZ * distance
1377 posIndex = posIndex + 3
1378 end
1379 end
1380 end
1381 end
1382end

createTurningSizeBox

Description
Definition
createTurningSizeBox()
Code
470function AITurnStrategy:createTurningSizeBox()
471 local box = {}
472 box.center = {0, 0, 0}
473 box.rotation = {0, 0, 0}
474 box.size = {0, 0, 0}
475
476 return box
477end

debugPrint

Description
Definition
debugPrint()
Code
1194function AITurnStrategy:debugPrint(text, ...)
1195 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
1196 print(string.format("AI DEBUG: %s", string.format(text, ...)))
1197 end
1198end

delete

Description
Definition
delete()
Code
64function AITurnStrategy:delete()
65 self:clearTurnSegments()
66end

drawTurnSegments

Description
Definition
drawTurnSegments()
Code
983function AITurnStrategy.drawTurnSegments(segments)
984 for i,segment in pairs(segments) do
985 if segment.isCurve == true then
986 local oX,oY,oZ = localToWorld(segment.o, 0,2,0)
987 local xX,xY,xZ = localToWorld(segment.o, 2,2,0)
988 local yX,yY,yZ = localToWorld(segment.o, 0,4,0)
989 local zX,zY,zZ = localToWorld(segment.o, 0,2,2)
990
991 drawDebugLine(oX,oY,oZ, 1,0,0, xX,xY,xZ, 1,0,0)
992 drawDebugLine(oX,oY,oZ, 0,1,0, yX,yY,yZ, 0,1,0)
993 drawDebugLine(oX,oY,oZ, 0,0,1, zX,zY,zZ, 0,0,1)
994
995 Utils.renderTextAtWorldPosition(yX,yY,yZ, tostring(i), 0.02, 0)
996
997 local ts = 20
998 for i=0,ts-1 do
999 local x1 = segment.radius * math.cos(segment.startAngle + i*(segment.endAngle-segment.startAngle)/ts)
1000 local z1 = segment.radius * math.sin(segment.startAngle + i*(segment.endAngle-segment.startAngle)/ts)
1001 local x2 = segment.radius * math.cos(segment.startAngle + (i+1)*(segment.endAngle-segment.startAngle)/ts)
1002 local z2 = segment.radius * math.sin(segment.startAngle + (i+1)*(segment.endAngle-segment.startAngle)/ts)
1003 local w1X,w1Y,w1Z = localToWorld(segment.o, x1,0,z1)
1004 local w2X,w2Y,w2Z = localToWorld(segment.o, x2,0,z2)
1005 local w1Y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, w1X,w1Y,w1Z) + 1
1006 local w2Y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, w2X,w2Y,w2Z) + 1
1007 drawDebugLine(w1X,w1Y,w1Z, (ts-i)/ts,i/ts,0, w2X,w2Y,w2Z, (ts-i-1)/ts,(i+1)/ts,0)
1008 end
1009 else
1010 local sY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, unpack(segment.startPoint)) + 1
1011 local eY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, unpack(segment.endPoint)) + 1
1012 drawDebugLine(segment.startPoint[1],sY,segment.startPoint[3], 1,0,0, segment.endPoint[1],eY,segment.endPoint[3], 0,1,0)
1013
1014 drawDebugLine(segment.startPoint[1],sY,segment.startPoint[3], 1,1,1, segment.startPoint[1],sY+2,segment.startPoint[3], 1,1,1)
1015 drawDebugLine(segment.endPoint[1],sY,segment.endPoint[3], 1,1,1, segment.endPoint[1],sY+2,segment.endPoint[3], 1,1,1)
1016
1017 Utils.renderTextAtWorldPosition((segment.startPoint[1]+segment.endPoint[1])/2, (sY+eY)/2, (segment.startPoint[3]+segment.endPoint[3])/2, tostring(i), 0.02, 0)
1018 end
1019 end
1020end

evaluateCollisionHits

Description
Definition
evaluateCollisionHits()
Code
1035function AITurnStrategy:evaluateCollisionHits(vX,vY,vZ, collisionHitLeft, collisionHitRight, turnData)
1036
1037 -- Remove the collision flag, as soon as one of the collisions is free again
1038 if not collisionHitLeft and self.collisionEndPosLeft ~= nil then
1039 local _,_,z = worldToLocal(self.vehicleDirectionNode, self.collisionEndPosLeft[1],self.collisionEndPosLeft[2],self.collisionEndPosLeft[3])
1040 if z < -1 then
1041 self.collisionEndPosLeft = nil
1042 self.collisionDetected = false
1043 self.collisionDetectedPosX = nil
1044 end
1045 end
1046 if not collisionHitRight and self.collisionEndPosRight ~= nil then
1047 local _,_,z = worldToLocal(self.vehicleDirectionNode, self.collisionEndPosRight[1],self.collisionEndPosRight[2],self.collisionEndPosRight[3])
1048 if z < -1 then
1049 self.collisionEndPosRight = nil
1050 self.collisionDetected = false
1051 self.collisionDetectedPosX = nil
1052 end
1053 end
1054
1055 -- self.turnLeft always refers to last direction, it is inverted during/before start of next turn
1056 if self.turnLeft == nil then
1057 if collisionHitLeft or collisionHitRight then
1058
1059 local allowLeft = self.usesExtraStraight == turnData.useExtraStraightLeft
1060 local allowRight = self.usesExtraStraight == turnData.useExtraStraightRight
1061
1062 if collisionHitLeft and collisionHitRight then
1063 self.collisionDetected = true
1064 else
1065 local leftAreaPercentage, rightAreaPercentage = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle, turnData)
1066 allowRight = allowRight and rightAreaPercentage >= leftAreaPercentage
1067 allowLeft = allowLeft and leftAreaPercentage >= rightAreaPercentage
1068
1069 -- if we hit a collision on one side we check if we can turn to the other side
1070 -- if the other side is also blocked (e.g. cause of there is no valid ground to work) we start to turn
1071 -- if the other side is free we use it as next turn direction
1072 if collisionHitLeft and not allowRight then
1073 self.collisionDetected = true
1074 self.turnLeft = false
1075 elseif collisionHitRight and not allowLeft then
1076 self.collisionDetected = true
1077 self.turnLeft = true
1078 else
1079 self.turnLeft = collisionHitLeft
1080 end
1081 end
1082 end
1083 else
1084 if self.turnLeft then
1085 if collisionHitRight then
1086 self.collisionDetected = true
1087 end
1088 else
1089 if collisionHitLeft then
1090 self.collisionDetected = true
1091 end
1092 end
1093 end
1094
1095 if self.collisionDetected and self.collisionDetectedPosX == nil then
1096 self.collisionDetectedPosX, self.collisionDetectedPosY, self.collisionDetectedPosZ = vX,vY,vZ
1097 end
1098end

getActiveTurnSegmentByNode

Description
Returns the current active turn segment based on a nodes position
Definition
getActiveTurnSegmentByNode()
Code
1224function AITurnStrategy:getActiveTurnSegmentByNode(referenceNode, originX, originY, originZ)
1225 local activeSegmentIndex
1226 local activeSegmentProgress = 0
1227 local minSegmentDistance = math.huge
1228 local activeSegment = self.turnSegments[self.activeTurnSegmentIndex]
1229 for i=self.activeTurnSegmentIndex, #self.turnSegments do
1230 local segment = self.turnSegments[i]
1231 if segment.moveForward and activeSegment.moveForward then
1232 if i >= self.activeTurnSegmentIndex and i <= self.activeTurnSegmentIndex + 2 then
1233 if segment.isCurve then
1234 local curAngle = AITurnStrategy.getAngleInSegment(referenceNode, segment, worldToLocal(referenceNode, originX, originY, originZ))
1235
1236 local x1, _, z1 = localToWorld(segment.o, segment.radius * math.cos(curAngle), 0, segment.radius * math.sin(curAngle))
1237 local distanceToSegment = MathUtil.vector2Length(x1 - originX, z1 - originZ)
1238 local prevAngle = curAngle - 2 * math.pi
1239 local nextAngle = curAngle + 2 * math.pi
1240 local outOfBoundsCur = MathUtil.getIsOutOfBounds(curAngle, segment.startAngle, segment.endAngle)
1241 local outOfBoundsPrev = MathUtil.getIsOutOfBounds(prevAngle, segment.startAngle, segment.endAngle)
1242 local outOfBoundsNext = MathUtil.getIsOutOfBounds(nextAngle, segment.startAngle, segment.endAngle)
1243 if not outOfBoundsCur or not outOfBoundsPrev or not outOfBoundsNext then
1244 if distanceToSegment < minSegmentDistance then
1245 activeSegmentIndex = i
1246 minSegmentDistance = distanceToSegment
1247
1248 if not outOfBoundsPrev then
1249 curAngle = prevAngle
1250 end
1251 if not outOfBoundsNext then
1252 curAngle = nextAngle
1253 end
1254
1255 activeSegmentProgress = (curAngle - segment.startAngle) / (segment.endAngle - segment.startAngle)
1256 end
1257 else
1258 x1, _, z1 = localToWorld(segment.o, segment.radius * math.cos(segment.startAngle), 0, segment.radius * math.sin(segment.startAngle))
1259 distanceToSegment = MathUtil.vector2Length(x1 - originX, z1 - originZ)
1260 if distanceToSegment < minSegmentDistance then
1261 activeSegmentIndex = i
1262 minSegmentDistance = distanceToSegment
1263 activeSegmentProgress = 0
1264 end
1265
1266 x1, _, z1 = localToWorld(segment.o, segment.radius * math.cos(segment.endAngle), 0, segment.radius * math.sin(segment.endAngle))
1267 distanceToSegment = MathUtil.vector2Length(x1 - originX, z1 - originZ)
1268 if distanceToSegment < minSegmentDistance then
1269 activeSegmentIndex = i
1270 minSegmentDistance = distanceToSegment
1271 activeSegmentProgress = 1
1272 end
1273 end
1274 else
1275 local x1, _, z1, pos = MathUtil.getClosestPointOnLineSegment(segment.startPoint[1], 0, segment.startPoint[3], segment.endPoint[1], 0, segment.endPoint[3], originX, 0, originZ)
1276 local distanceToSegment = MathUtil.vector2Length(x1 - originX, z1 - originZ)
1277 if distanceToSegment < minSegmentDistance then
1278 activeSegmentIndex = i
1279 minSegmentDistance = distanceToSegment
1280
1281 activeSegmentProgress = pos
1282 end
1283 end
1284 end
1285 else
1286 break
1287 end
1288 end
1289
1290 return activeSegmentIndex, activeSegmentProgress
1291end

getAngleInSegment

Description
Definition
getAngleInSegment()
Code
974function AITurnStrategy.getAngleInSegment(node, segment, ox, oy, oz)
975 ox, oy, oz = ox or 0, oy or 0, oz or 0
976 local vX, _, vZ = localToLocal(node, segment.o, ox, oy, oz)
977
978 return math.atan2(vZ, vX)
979end

getCollisionBoxSlope

Description
Definition
getCollisionBoxSlope()
Code
688function AITurnStrategy:getCollisionBoxSlope(rootNode, x1, y1, z1, x2, y2, z2)
689 x1, y1, z1 = localToWorld(self.vehicleDirectionNode, x1, y1, z1)
690 x2, y2, z2 = localToWorld(self.vehicleDirectionNode, x2, y2, z2)
691
692 local terrain1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1, 0, z1)
693 local terrain2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2, 0, z2)
694
695 local length = MathUtil.vector3Length(x1-x2, y1-y2, z1-z2)
696 local angleBetween = math.atan(math.abs(terrain1-terrain2)/length)
697
698 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
699 self.vehicle:addAIDebugLine({x1, terrain1+1, z1}, {x2, terrain2+1, z2}, {1,0,0})
700 Utils.renderTextAtWorldPosition((x1+x2)/2, (terrain1+1+terrain2+1)/2, (z1+z2)/2, string.format("angle: %.1f", math.deg(angleBetween)), getCorrectTextSize(0.012), 0)
701 end
702
703 return angleBetween
704end

getDistanceToCollision

Description
Definition
getDistanceToCollision()
Code
481function AITurnStrategy:getDistanceToCollision(dt, vX,vY,vZ, turnData, lookAheadDistance)
482 local allowLeft = self.usesExtraStraight == turnData.useExtraStraightLeft
483 local allowRight = self.usesExtraStraight == turnData.useExtraStraightRight
484
485 local distanceToTurn = lookAheadDistance
486
487 -- Disable this turn strategy immediately if we are supposed to turn in a direction that is not allwoed
488 -- or if the only allowed side does not have any work to be done
489 if not allowLeft and not allowRight then
490 distanceToTurn = -1
491 elseif self.turnLeft ~= nil then
492 if (self.turnLeft and not allowRight) or (not self.turnLeft and not allowLeft) then
493 distanceToTurn = -1
494 end
495 else
496 local allowLeftWithCol = allowLeft and self.collisionEndPosLeft == nil
497 local allowRightWithCol = allowRight and self.collisionEndPosRight == nil
498
499 -- Turn if not allowed or has collision on one side and the other side has no work to be done
500 if not allowLeftWithCol or not allowRightWithCol then
501 local leftAreaPercentage, rightAreaPercentage = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle, turnData)
502
503 if allowLeftWithCol and leftAreaPercentage <= 3*AIVehicleUtil.VALID_AREA_THRESHOLD then
504 distanceToTurn = -1
505 end
506 if allowRightWithCol and rightAreaPercentage <= 3*AIVehicleUtil.VALID_AREA_THRESHOLD then
507 distanceToTurn = -1
508 end
509 end
510 end
511
512 if self.collisionDetected then
513 if self.collisionDetectedPosX ~= nil then
514 local dist = MathUtil.vector3Length(vX-self.collisionDetectedPosX, vY-self.collisionDetectedPosY, vZ-self.collisionDetectedPosZ)
515 distanceToTurn = math.min(distanceToTurn, lookAheadDistance - dist)
516 else
517 distanceToTurn = -1
518 end
519 end
520
521 self.distanceToCollision = distanceToTurn
522
523 -- increase the size of the collision check boxes more to the back
524 -- this helps to avoid issues if an object is between the field end and the vehicle direction node -> so the boxes cover also 5m behind the direction node
525 local boxLookBackDistance = 0
526 if self.parent ~= nil then
527 if self.parent.rowStartTranslation ~= nil then
528 local x, _, z = getWorldTranslation(self.vehicle:getAIDirectionNode())
529 boxLookBackDistance = math.min(MathUtil.vector2Length(x-self.parent.rowStartTranslation[1], z-self.parent.rowStartTranslation[3]) * 0.5, 5)
530 end
531 end
532
533 --
534 for i=#self.maxTurningSizeBoxes, 1, -1 do
535 self.maxTurningSizeBoxes[i] = nil
536 end
537
538 local collisionHitLeft = false
539 local collisionHitRight = false
540
541 if (self.turnLeft == nil or self.turnLeft == false) and allowLeft then
542 local turnLeft = true
543 self:updateTurningSizeBox(self.leftBox, turnLeft, turnData, math.max(0,distanceToTurn))
544 local box = self.leftBox
545 box.center[3] = box.center[3]-boxLookBackDistance
546 box.size[3] = box.size[3]+boxLookBackDistance
547
548 if not self:validateCollisionBox(box) then
549 self.vehicle:stopCurrentAIJob(AIMessageErrorUnknown.new())
550 self:debugPrint("Stopping AIVehicle - collision box invalid")
551 return distanceToTurn
552 end
553
554 collisionHitLeft = self:getIsBoxColliding(box)
555
556 table.insert(self.maxTurningSizeBoxes, box)
557
558 if collisionHitLeft and self.collisionEndPosLeft == nil then
559 self.collisionEndPosLeft = { localToWorld(self.vehicleDirectionNode, 0,0,box.size[3]) }
560 end
561 end
562
563 if (self.turnLeft == nil or self.turnLeft == true) and allowRight then
564 local turnLeft = false
565 self:updateTurningSizeBox(self.rightBox, turnLeft, turnData, math.max(0,distanceToTurn))
566 local box = self.rightBox
567 box.center[3] = box.center[3]-boxLookBackDistance
568 box.size[3] = box.size[3]+boxLookBackDistance
569
570 if not self:validateCollisionBox(box) then
571 self.vehicle:stopCurrentAIJob(AIMessageErrorUnknown.new())
572 self:debugPrint("Stopping AIVehicle - collision box invalid 2")
573 return distanceToTurn
574 end
575
576 collisionHitRight = self:getIsBoxColliding(box)
577
578 table.insert(self.maxTurningSizeBoxes, box)
579
580 if collisionHitRight and self.collisionEndPosRight == nil then
581 self.collisionEndPosRight = { localToWorld(self.vehicleDirectionNode, 0,0,box.size[3]) }
582 end
583 end
584
585 self:evaluateCollisionHits(vX,vY,vZ, collisionHitLeft, collisionHitRight, turnData)
586
587 return distanceToTurn
588end

getDriveData

Description
Definition
getDriveData()
Code
169function AITurnStrategy:getDriveData(dt, vX,vY,vZ, turnData)
170 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
171 self.vehicle:addAIDebugText(string.format("strategy: %s", self.strategyName))
172 end
173
174 local tX, tY, tZ
175 local maxSpeed = self.vehicle:getSpeedLimit(true)
176 maxSpeed = math.min(14, maxSpeed)
177 local distanceToStop
178
179 local segment = self.turnSegments[self.activeTurnSegmentIndex]
180 local segmentIsFinished = false
181
182 local moveForwards = segment.moveForward
183
184 if segment.isCurve then
185 local angleDirSign = MathUtil.sign(segment.endAngle - segment.startAngle)
186
187 local curAngle
188 if self.reverserDirectionNode ~= nil and not moveForwards then
189 curAngle = AITurnStrategy.getAngleInSegment(self.reverserDirectionNode, segment)
190 else
191 curAngle = AITurnStrategy.getAngleInSegment(self.vehicleAISteeringNode, segment)
192 end
193
194 local nextAngleDistance = math.max(3, 0.33 * self.vehicle.maxTurningRadius)
195
196 local nextAngle = curAngle + angleDirSign * nextAngleDistance / segment.radius
197 if nextAngle > math.pi then
198 nextAngle = nextAngle - 2*math.pi
199 elseif nextAngle < -math.pi then
200 nextAngle = nextAngle + 2*math.pi
201 end
202
203 local endAngle = segment.endAngle
204 if endAngle > math.pi then
205 endAngle = endAngle - 2*math.pi
206 elseif endAngle < -math.pi then
207 endAngle = endAngle + 2*math.pi
208 end
209
210 angleDirSign = MathUtil.sign(segment.endAngle - segment.startAngle)
211
212 local curAngleDiff = angleDirSign * (curAngle - endAngle) -- > 0 if after endAngle
213 if curAngleDiff > math.rad(10) then
214 curAngleDiff = curAngleDiff - 2*math.pi
215 elseif curAngleDiff < -2*math.pi + math.rad(10) then
216 curAngleDiff = curAngleDiff + 2*math.pi
217 end
218
219 local nextAngleDiff = angleDirSign * (nextAngle - endAngle)
220 if nextAngleDiff > math.rad(10) then
221 nextAngleDiff = nextAngleDiff - 2*math.pi
222 elseif nextAngleDiff < -2*math.pi + math.rad(10) then
223 nextAngleDiff = nextAngleDiff + 2*math.pi
224 end
225
226 local pX = math.cos(nextAngle) * segment.radius
227 local pZ = math.sin(nextAngle) * segment.radius
228 tX,tY,tZ = localToWorld(segment.o, pX,0,pZ)
229
230 -- condition for segment finish
231 distanceToStop = -curAngleDiff * segment.radius
232
233 if distanceToStop < 0.01 or (segment.usePredictionToSkipToNextSegment ~= false and nextAngleDiff > 0) then
234 segmentIsFinished = true
235 end
236
237 if segment.checkForSkipToNextSegment then
238 local nextSegment = self.turnSegments[self.activeTurnSegmentIndex+1]
239
240 local dirX = nextSegment.endPoint[1] - nextSegment.startPoint[1]
241 local dirZ = nextSegment.endPoint[3] - nextSegment.startPoint[3]
242 local dirLength = MathUtil.vector2Length(dirX, dirZ)
243 local dx,_
244 if self.reverserDirectionNode ~= nil and not moveForwards then
245 dx,_,_ = worldDirectionToLocal(self.reverserDirectionNode, dirX/dirLength,0,dirZ/dirLength)
246 else
247 dx,_,_ = worldDirectionToLocal(self.vehicleAISteeringNode, dirX/dirLength,0,dirZ/dirLength)
248 end
249
250 local l = MathUtil.vector2Length(dirX, dirZ)
251 dirX, dirZ = dirX / l, dirZ / l
252 pX, pZ = MathUtil.projectOnLine(vX, vZ, nextSegment.startPoint[1], nextSegment.startPoint[3], dirX, dirZ)
253 local dist = MathUtil.vector2Length(vX-pX, vZ-pZ)
254
255 local distToStart = MathUtil.vector2Length(vX-nextSegment.startPoint[1], vZ-nextSegment.startPoint[3])
256
257 if dist < 1.5 and math.abs(dx) < 0.15 and distToStart < self.vehicle.size.length/2 then
258 segmentIsFinished = true
259 end
260 end
261
262 --
263 if not moveForwards and self.reverserDirectionNode ~= nil then
264 local x,_,z = worldToLocal(self.reverserDirectionNode, tX,vY,tZ)
265 local alpha = Utils.getYRotationBetweenNodes(self.vehicleAISteeringNode, self.reverserDirectionNode)
266 local ltX = math.cos(alpha)*x - math.sin(alpha)*z
267 local ltZ = math.sin(alpha)*x + math.cos(alpha)*z
268 ltX = -ltX
269 tX,_,tZ = localToWorld(self.vehicleAISteeringNode, ltX,0,ltZ)
270 end
271
272 -- just visual debuging
273 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
274 drawDebugLine(vX,vY+2,vZ, 1,1,0, tX,tY+2,tZ, 1,1,0)
275 end
276 else
277 local toolX,_,toolZ
278 if self.reverserDirectionNode ~= nil then
279 toolX,_,toolZ = getWorldTranslation(self.reverserDirectionNode)
280 end
281
282 local dirX = segment.endPoint[1] - segment.startPoint[1]
283 local dirZ = segment.endPoint[3] - segment.startPoint[3]
284
285 local l = MathUtil.vector2Length(dirX, dirZ)
286 dirX, dirZ = dirX / l, dirZ / l
287 local pX, pZ
288 if self.reverserDirectionNode ~= nil and not moveForwards then
289 pX, pZ = MathUtil.projectOnLine(toolX, toolZ, segment.startPoint[1], segment.startPoint[3], dirX, dirZ)
290 else
291 if self.vehicleAIReverserNode ~= nil and not moveForwards then
292 toolX,_,toolZ = getWorldTranslation(self.vehicleAIReverserNode)
293 pX, pZ = MathUtil.projectOnLine(toolX, toolZ, segment.startPoint[1], segment.startPoint[3], dirX, dirZ)
294 else
295 pX, pZ = MathUtil.projectOnLine(vX, vZ, segment.startPoint[1], segment.startPoint[3], dirX, dirZ)
296 end
297 end
298
299 local factor = 1.0
300 tX = pX + (dirX * factor * self.vehicle.maxTurningRadius)
301 tZ = pZ + (dirZ * factor * self.vehicle.maxTurningRadius)
302
303 if self.reverserDirectionNode ~= nil and not moveForwards then
304 local x,_,z = worldToLocal(self.reverserDirectionNode, tX,vY,tZ)
305 local alpha = Utils.getYRotationBetweenNodes(self.vehicleAISteeringNode, self.reverserDirectionNode)
306
307 local articulatedAxisSpec = self.vehicle.spec_articulatedAxis
308 if articulatedAxisSpec ~= nil and articulatedAxisSpec.componentJoint ~= nil then
309 local node1 = self.vehicle.components[articulatedAxisSpec.componentJoint.componentIndices[1]].node
310 local node2 = self.vehicle.components[articulatedAxisSpec.componentJoint.componentIndices[2]].node
311 if articulatedAxisSpec.anchorActor == 1 then
312 node1, node2 = node2, node1
313 end
314
315 local beta = Utils.getYRotationBetweenNodes(node1, node2)
316 alpha = alpha - beta
317 end
318
319 local ltX = math.cos(alpha)*x - math.sin(alpha)*z
320 local ltZ = math.sin(alpha)*x + math.cos(alpha)*z
321 ltX = -ltX
322 tX,_,tZ = localToWorld(self.vehicleAISteeringNode, ltX,0,ltZ)
323 end
324
325 distanceToStop = MathUtil.vector3Length(segment.endPoint[1]-vX, segment.endPoint[2]-vY, segment.endPoint[3]-vZ)
326
327
328 local _,_,lz = worldToLocal(self.vehicleAISteeringNode, segment.endPoint[1],segment.endPoint[2],segment.endPoint[3])
329 if (segment.moveForward and lz < 0) or (not segment.moveForward and lz > 0) then
330 segmentIsFinished = true
331 end
332
333 -- check during pre final straight, only used by reverse strategies
334 if segment.checkAlignmentToSkipSegment then
335 local d1x,_,d1z = localDirectionToWorld(self.vehicleAISteeringNode, 0,0,1)
336 local l1 = MathUtil.vector2Length(d1x, d1z)
337 d1x, d1z = d1x/l1, d1z/l1
338 local a1 = math.acos( d1x * dirX + d1z * dirZ )
339 local dist = MathUtil.vector2Length(vX-pX, vZ-pZ)
340 local canSkip = math.deg(a1) < 8 and dist < 0.6
341
342 if self.vehicle.spec_articulatedAxis ~= nil and self.vehicle.spec_articulatedAxis.componentJoint ~= nil then
343 for i=1,2 do
344 local node = self.vehicle.components[self.vehicle.spec_articulatedAxis.componentJoint.componentIndices[i]].node
345
346 d1x,_,d1z = localDirectionToWorld(node, 0,0,1)
347 l1 = MathUtil.vector2Length(d1x, d1z)
348 d1x, d1z = d1x/l1, d1z/l1
349 local a = math.acos( d1x * dirX + d1z * dirZ )
350 canSkip = canSkip and math.deg(a) < 8
351 end
352 end
353
354 if self.reverserDirectionNode ~= nil then
355 local d2x,_,d2z = localDirectionToWorld(self.reverserDirectionNode, 0,0,1)
356 local l2 = MathUtil.vector2Length(d2x, d2z)
357 d2x, d2z = d2x/l2, d2z/l2
358 local a2 = math.acos( d2x * dirX + d2z * dirZ )
359 pX, pZ = MathUtil.projectOnLine(toolX,toolZ, segment.startPoint[1],segment.startPoint[3], dirX,dirZ)
360 dist = MathUtil.vector2Length(toolX-pX, toolZ-pZ)
361 canSkip = canSkip and math.deg(a2) < 6 and dist < 0.6
362 end
363
364 local nextSegment = self.turnSegments[self.activeTurnSegmentIndex+1]
365 local _,_,sz = worldToLocal(self.vehicleDirectionNode, nextSegment.startPoint[1],nextSegment.startPoint[2],nextSegment.startPoint[3])
366 local _,_,ez = worldToLocal(self.vehicleDirectionNode, nextSegment.endPoint[1],nextSegment.endPoint[2],nextSegment.endPoint[3])
367 canSkip = canSkip and (sz < 0 or ez < 0)
368
369 if canSkip then
370 segmentIsFinished = true
371 end
372 end
373
374 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
375 local sY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, tX,vY,tZ)
376 drawDebugLine(vX,vY+2,vZ, 1,1,0, tX,sY+2,tZ, 1,1,0)
377 end
378 end
379
380 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
381 self.vehicle:addAIDebugText(string.format("active segment: %d", self.activeTurnSegmentIndex))
382 end
383
384 --# check if a tool can already work
385 if segment.checkForValidArea then
386 local lookAheadDist = 0
387 local lookAheadSize = 1
388 if not moveForwards then
389 lookAheadSize = -1
390 end
391 if AIVehicleUtil.checkImplementListForValidGround(self.vehicle, lookAheadDist, lookAheadSize) then
392 segmentIsFinished = true
393 self.activeTurnSegmentIndex = #self.turnSegments
394 end
395 end
396
397 --#
398 if segment.findEndOfField then
399 local lookAheadDist = 0
400 local lookAheadSize = 1
401 if not moveForwards then
402 lookAheadSize = -1
403 end
404 if not AIVehicleUtil.checkImplementListForValidGround(self.vehicle, lookAheadDist, lookAheadSize) then
405 segmentIsFinished = true
406 end
407 end
408
409 -- activate next segment or stop turn
410 if segmentIsFinished or self.requestToEndTurn then
411 self.activeTurnSegmentIndex = self.activeTurnSegmentIndex + 1
412
413 if self.turnSegments[self.activeTurnSegmentIndex] == nil then
414 self.isTurning = false
415 self.requestToEndTurn = false
416 return nil
417 end
418 end
419
420 -- calculate turn progress percentage
421 local totalSegmentLength = 0
422 local usedSegmentDistance = 0
423 for i, turnSegment in ipairs(self.turnSegments) do
424 -- exclude the last straight part(s) since we don't know how far we need to go
425 if turnSegment.checkAlignmentToSkipSegment then
426 break
427 end
428
429 local segmentLength
430 if turnSegment.isCurve then
431 segmentLength = math.abs(turnSegment.endAngle - turnSegment.startAngle) * turnSegment.radius
432 else
433 segmentLength = math.abs(MathUtil.vector3Length(turnSegment.endPoint[1]-turnSegment.startPoint[1],
434 turnSegment.endPoint[2]-turnSegment.startPoint[2],
435 turnSegment.endPoint[3]-turnSegment.startPoint[3]))
436 end
437 segmentLength = math.abs(segmentLength)
438
439 totalSegmentLength = totalSegmentLength + segmentLength
440
441 if i < self.activeTurnSegmentIndex then
442 usedSegmentDistance = usedSegmentDistance + segmentLength
443 elseif i == self.activeTurnSegmentIndex then
444 usedSegmentDistance = usedSegmentDistance + (segmentLength-distanceToStop)
445 end
446 end
447
448 local turnProgress = usedSegmentDistance / totalSegmentLength
449 self.vehicle:aiFieldWorkerTurnProgress(turnProgress, self.turnLeft)
450 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
451 self.vehicle:addAIDebugText(string.format("turn progress: %.1f%%", turnProgress*100))
452 end
453
454 if not segment.slowDown then
455 distanceToStop = math.huge
456 end
457
458 return tX, tZ, moveForwards, maxSpeed, distanceToStop
459end

getIsBoxColliding

Description
Definition
getIsBoxColliding()
Code
592function AITurnStrategy:getIsBoxColliding(box)
593 box.x, box.y, box.z = localToWorld(self.vehicleDirectionNode, box.center[1], box.center[2], box.center[3])
594 box.zx, box.zy, box.zz = localDirectionToWorld(self.vehicleDirectionNode, 0,0,1)
595 box.xx, box.xy, box.xz = localDirectionToWorld(self.vehicleDirectionNode, 1,0,0)
596 box.ry = math.atan2(box.zx, box.zz)
597 box.color = AITurnStrategy.COLLISION_BOX_COLOR_OK
598
599 self.collisionHit = false
600 overlapBox(box.x,box.y,box.z, 0,box.ry,0, box.size[1],box.size[2],box.size[3], "collisionTestCallback", self, CollisionFlag.AI_BLOCKING, true, true, true)
601 if self.collisionHit then
602 box.color = AITurnStrategy.COLLISION_BOX_COLOR_HIT
603 return true
604 end
605
606 local x1, _, z1 = localToWorld(self.vehicleDirectionNode, box.center[1]+box.size[1], 0, box.center[3]+box.size[3])
607 local x2, _, z2 = localToWorld(self.vehicleDirectionNode, box.center[1]+box.size[1], 0, box.center[3]-box.size[3])
608
609 local t1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1, 0, z1)
610 local t2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2, 0, z2)
611
612 -- water check has always one frame delay (async)
613 raycastClosest(x1, t1, z1, 0, -1, 0, "onWaterRaycastCallback", 200, self, CollisionFlag.WATER, false, false, true)
614 raycastClosest(x2, t2, z2, 0, -1, 0, "onWaterRaycastCallback2", 200, self, CollisionFlag.WATER, false, false, true)
615
616 local waterY1 = self.lastWaterY1
617 local waterY2 = self.lastWaterY2
618 self.lastWaterY1 = -2000
619 self.lastWaterY2 = -2000
620
621 -- check if at the box start or end position is water
622 if t1 < waterY1 or t2 < waterY2 then
623 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
624 self.vehicle:addAIDebugText(string.format(" hit water: b%.1f f%.1f w%.1f", t1, t2, waterY1))
625 end
626
627 box.color = AITurnStrategy.COLLISION_BOX_COLOR_HIT
628 return true
629 end
630
631 local testLength = 3
632
633 -- left and right side of the box
634 local angle1 = self:getCollisionBoxSlope(self.vehicleDirectionNode, box.center[1]+box.size[1], 0, box.center[3]+box.size[3], box.center[1]+box.size[1], 0, box.center[3]+box.size[3]-testLength)
635 local angle2 = self:getCollisionBoxSlope(self.vehicleDirectionNode, box.center[1]-box.size[1], 0, box.center[3]+box.size[3], box.center[1]-box.size[1], 0, box.center[3]+box.size[3]-testLength)
636
637 -- center of vehicle
638 local angle3 = self:getCollisionBoxSlope(self.vehicleDirectionNode, 0, 0, box.center[3]+box.size[3], 0, 0, box.center[3]+box.size[3]-testLength)
639
640 -- side angle of box
641 local angle4 = self:getCollisionBoxSlope(self.vehicleDirectionNode, box.center[1]+box.size[1]-testLength, 0, box.center[3]+box.size[3], box.center[1]+box.size[1], 0, box.center[3]+box.size[3])
642 local angle5 = self:getCollisionBoxSlope(self.vehicleDirectionNode, box.center[1]-box.size[1]+testLength, 0, box.center[3]+box.size[3], box.center[1]-box.size[1], 0, box.center[3]+box.size[3])
643
644 local angleBetween = math.max(angle1, angle2, angle3, angle4, angle5)
645
646 if angleBetween > AITurnStrategy.SLOPE_DETECTION_THRESHOLD then
647 box.color = AITurnStrategy.COLLISION_BOX_COLOR_HIT
648 return true
649 end
650
651 -- check for density height heaps higher than 2m
652 x1, _, z1 = localToWorld(self.vehicleDirectionNode, box.center[1]+box.size[1], 0, box.center[3]+box.size[3])
653 x2, _, z2 = localToWorld(self.vehicleDirectionNode, box.center[1]-box.size[1], 0, box.center[3]+box.size[3])
654 local length = MathUtil.vector2Length(x1-x2, z1-z2)
655 local steps = math.floor(length/3)
656 for i=0,steps do
657 local alpha = math.min(1/(steps+1)*(i+math.random()), 1)
658 local x, z = MathUtil.lerp(x1, x2, alpha), MathUtil.lerp(z1, z2, alpha)
659 local _, densityHeight = DensityMapHeightUtil.getHeightAtWorldPos(x, 0, z)
660
661 if densityHeight >= AITurnStrategy.DENSITY_HEIGHT_THRESHOLD then
662 box.color = AITurnStrategy.COLLISION_BOX_COLOR_HIT
663 return true
664 end
665 end
666
667 return false
668end

getNoFullCoverageZOffset

Description
Definition
getNoFullCoverageZOffset()
Code
1144function AITurnStrategy:getNoFullCoverageZOffset()
1145 local offset = 0
1146
1147 if AIVehicleUtil.getAttachedImplementsBlockTurnBackward(self.vehicle) then
1148 return 0
1149 end
1150
1151 local attachedAIImplements = self.vehicle:getAttachedAIImplements()
1152 for _, implement in pairs(attachedAIImplements) do
1153 if implement.object:getAIHasNoFullCoverageArea() then
1154 local leftMarker, _, backMarker = implement.object:getAIMarkers()
1155 local _, _, markerZOffset = localToLocal(backMarker, leftMarker, 0,0,0)
1156 offset = offset + markerZOffset
1157 end
1158 end
1159
1160 offset = offset + self.corridorPositionOffset
1161 offset = offset + self.lastValidTurnPositionOffset
1162
1163 return offset
1164end

getTurnRadius

Description
Returns turn radius (uses sideOffset as radius if it's just slightly bigger)
Definition
getTurnRadius()
Code
1202function AITurnStrategy:getTurnRadius(radius, sideOffset)
1203 -- if we are 25cm or less of when we do a direct turn we do this
1204 -- this saves us time because we can compensate these 25cm easily since we have a overlap anyway
1205 if math.abs(math.abs(sideOffset) - radius) < 0.25 then
1206 radius = math.abs(sideOffset)
1207 end
1208
1209 return radius
1210end

getVehicleToWorld

Description
Definition
getVehicleToWorld()
Code
1168function AITurnStrategy:getVehicleToWorld(x, y, z, returnTable)
1169 x, y, z = localToWorld(self.vehicleDirectionNode, x, y, z+self:getNoFullCoverageZOffset())
1170 if returnTable then
1171 return {x, y, z}
1172 end
1173
1174 return x, y, z
1175end

getZOffsetForTurn

Description
Definition
getZOffsetForTurn()
Code
889function AITurnStrategy:getZOffsetForTurn(box0)
890 local box = {name="ZoffsetForTurn", center={box0.center[1],box0.center[2],box0.center[3]}, size={box0.size[1],box0.size[2],box0.size[3]}}
891
892 local length = math.max( self.distanceToCollision + 2 * box0.size[3], 20)
893
894 box.center[3] = length/2
895 box.size[3] = length/2
896 box.vFront = { localDirectionToWorld(self.vehicleDirectionNode, 0,0,1) }
897 box.vLeft = { localDirectionToWorld(self.vehicleDirectionNode, 1,0,0) }
898
899 local zOffset = self.distanceToCollision
900
901 local i = 0
902 while box.size[3] > 0.5 do
903 self.collisionHit = self:getIsBoxColliding(box)
904
905 if self.collisionHit then
906 box.center[3] = box.center[3] - box.size[3]/2
907 else
908 zOffset = box.center[3] + box.size[3]
909 box.center[3] = box.center[3] + 3*box.size[3]/2
910 end
911 box.size[3] = box.size[3]/2
912
913 i = i + 1
914 end
915
916 return zOffset
917end

new

Description
Definition
new()
Code
18function AITurnStrategy.new(customMt)
19 if customMt == nil then
20 customMt = AITurnStrategy_mt
21 end
22
23 local self = {}
24 setmetatable(self, customMt)
25
26 self.isTurning = false
27 self.turnLeft = nil
28
29 self.collisionDetected = false
30
31 self.usesExtraStraight = false
32
33 self.distanceToCollision = 5
34
35 self.maxTurningSizeBoxes = {}
36 self.maxTurningSizeBoxes2 = {}
37
38 self.turnSegments = {}
39
40 self.requestToEndTurn = false
41 self.lastValidTurnPositionOffset = 0
42 self.corridorPositionOffset = 0
43
44 self.strategyName = "AITurnStrategy"
45
46 self.leftBox = self:createTurningSizeBox()
47 self.rightBox = self:createTurningSizeBox()
48
49 self.heightChecks = {}
50 table.insert(self.heightChecks, {1, 1})
51 table.insert(self.heightChecks, {-1, 1})
52 table.insert(self.heightChecks, {1, -1})
53 table.insert(self.heightChecks, {-1, -1})
54 self.numHeightChecks = 4
55
56 self.lastWaterY1 = -2000
57 self.lastWaterY2 = -2000
58
59 return self
60end

onEndTurn

Description
Definition
onEndTurn()
Code
949function AITurnStrategy:onEndTurn(turnLeft)
950 if #self.turnSegments > 0 then
951 self.vehicle:aiFieldWorkerEndTurn(self.turnLeft)
952 end
953
954 --#
955 self.collisionDetected = false
956 self.collisionEndPosLeft = nil
957 self.collisionEndPosRight = nil
958 self.collisionDetectedPosX = nil
959 self.turnLeft = turnLeft
960 self.maxTurningSizeBoxes = {}
961 self.maxTurningSizeBoxes2 = {}
962
963 self:clearTurnSegments()
964
965 AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, self.vehicle)
966 for _,implement in pairs(self.vehicle:getAttachedAIImplements()) do
967 AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, implement.object)
968 end
969end

onWaterRaycastCallback

Description
Definition
onWaterRaycastCallback()
Code
672function AITurnStrategy:onWaterRaycastCallback(hitObjectId, x, y, z, distance, nx, ny, nz, subShapeIndex, shapeId, isLast)
673 if hitObjectId ~= 0 then
674 self.lastWaterY1 = y
675 end
676end

onWaterRaycastCallback2

Description
Definition
onWaterRaycastCallback2()
Code
680function AITurnStrategy:onWaterRaycastCallback2(hitObjectId, x, y, z, distance, nx, ny, nz, subShapeIndex, shapeId, isLast)
681 if hitObjectId ~= 0 then
682 self.lastWaterY2 = y
683 end
684end

setAIVehicle

Description
Definition
setAIVehicle()
Code
70function AITurnStrategy:setAIVehicle(vehicle, parent)
71 self.vehicle = vehicle
72 self.vehicleDirectionNode = self.vehicle:getAIDirectionNode()
73 self.vehicleAISteeringNode = self.vehicle:getAISteeringNode()
74 self.vehicleAIReverserNode = self.vehicle:getAIReverserNode()
75 self.reverserDirectionNode = AIVehicleUtil.getAIToolReverserDirectionNode(self.vehicle)
76 self.parent = parent
77
78 AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, self.vehicle)
79 for _,implement in pairs(self.vehicle:getAttachedAIImplements()) do
80 AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, implement.object)
81 end
82end

skipTurnSegment

Description
Skip current turn segment
Definition
skipTurnSegment()
Code
1214function AITurnStrategy:skipTurnSegment()
1215 if self.activeTurnSegmentIndex < #self.turnSegments then
1216 self.activeTurnSegmentIndex = self.activeTurnSegmentIndex + 1
1217 else
1218 self.requestToEndTurn = true
1219 end
1220end

startTurn

Description
Definition
startTurn()
Code
731function AITurnStrategy:startTurn(driveStrategyStraight)
732
733 local turnData = driveStrategyStraight.turnData
734
735 self.isTurning = true
736 self.requestToEndTurn = false
737
738 self:clearTurnSegments()
739
740 self.turnSegmentsTotalLength = 0
741 self.activeTurnSegmentIndex = 1
742
743 local allowLeft = self.usesExtraStraight == turnData.useExtraStraightLeft and driveStrategyStraight.gabAllowTurnLeft
744 local allowRight = self.usesExtraStraight == turnData.useExtraStraightRight and driveStrategyStraight.gabAllowTurnRight
745 if not allowLeft and not allowRight then
746 self:debugPrint("Stopping AI - not allowed in both directions (gabAllowTurnLeft: %s, gabAllowTurnRight: %s)", driveStrategyStraight.gabAllowTurnLeft, driveStrategyStraight.gabAllowTurnRight)
747 return false
748 end
749
750 --#
751 AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, self.vehicle)
752 for _,implement in pairs(self.vehicle:getAttachedAIImplements()) do
753 AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, implement.object)
754 end
755
756 -- determine turn direction
757 local leftAreaPercentage, rightAreaPercentage = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle, turnData)
758
759 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
760 log(" --(I)--> self.turnLeft:", self.turnLeft, "leftAreaPercentage:", leftAreaPercentage, "rightAreaPercentage:", rightAreaPercentage)
761 end
762
763 if driveStrategyStraight.corridorDistance ~= nil then
764 self.corridorPositionOffset = -driveStrategyStraight.corridorDistance
765 end
766
767 local checkForLastValidPosition = function(vehicleNode, turnLeft, threshold)
768 if turnLeft and not allowLeft then
769 return false
770 end
771 if not turnLeft and not allowRight then
772 return false
773 end
774
775 local position = driveStrategyStraight.lastValidTurnLeftPosition
776 if not turnLeft then
777 position = driveStrategyStraight.lastValidTurnRightPosition
778 end
779
780 if position[1] ~= 0 and position[2] ~= 0 and position[3] ~= 0 then
781 local x, y, z = unpack(driveStrategyStraight.lastValidTurnCheckPosition)
782 local distance = MathUtil.vector3Length(position[1]-x, position[2]-y, position[3]-z)
783 if distance > threshold then
784 self.lastValidTurnPositionOffset = -distance
785
786 return true
787 end
788 end
789 end
790
791 if self.turnLeft == nil then
792 -- if both side are equal we prefer the opossite of the last used turn direction
793 if leftAreaPercentage == rightAreaPercentage then
794 if self.vehicle:getAIFieldWorkerLastTurnDirection() then
795 rightAreaPercentage = rightAreaPercentage + 0.01
796 else
797 leftAreaPercentage = leftAreaPercentage + 0.01
798 end
799 end
800
801 local forcePreferLeft = self.collisionEndPosLeft == nil and self.collisionEndPosRight ~= nil
802 local forcePreferRight = self.collisionEndPosRight == nil and self.collisionEndPosLeft ~= nil and allowRight and rightAreaPercentage > AIVehicleUtil.VALID_AREA_THRESHOLD
803 local preferLeft = ((leftAreaPercentage > rightAreaPercentage or forcePreferLeft) and not forcePreferRight)
804
805 if allowLeft and leftAreaPercentage > AIVehicleUtil.VALID_AREA_THRESHOLD and (preferLeft or not allowRight) then
806 self.turnLeft = true
807
808 -- still check for last valid turn position since the distance to the field could be greater than 5m
809 checkForLastValidPosition(self.vehicleDirectionNode, true, 5)
810 elseif allowRight and rightAreaPercentage > AIVehicleUtil.VALID_AREA_THRESHOLD then
811 self.turnLeft = false
812
813 -- still check for last valid turn position since the distance to the field could be greater than 5m
814 checkForLastValidPosition(self.vehicleDirectionNode, false, 5)
815 else
816 if not checkForLastValidPosition(self.vehicleDirectionNode, true, 5) then
817 if not checkForLastValidPosition(self.vehicleDirectionNode, false, 5) then
818 self:debugPrint("Stopping AIVehicle - no valid ground (I)")
819 return false
820 else
821 self.turnLeft = false
822 end
823 else
824 self.turnLeft = true
825 end
826 end
827 else
828 -- first, switch turn direction
829 self.turnLeft = not self.turnLeft
830
831 if self.turnLeft then
832 if not allowLeft or leftAreaPercentage < AIVehicleUtil.VALID_AREA_THRESHOLD then
833 if not checkForLastValidPosition(self.vehicleDirectionNode, true, 5) then
834 self:debugPrint("Stopping AI - No ground left (%.3f)", leftAreaPercentage)
835 return false
836 end
837 else
838 -- still check for last valid turn position since the distance to the field could be greater than 5m
839 checkForLastValidPosition(self.vehicleDirectionNode, true, 5)
840 end
841 else
842 if not allowRight or rightAreaPercentage < AIVehicleUtil.VALID_AREA_THRESHOLD then
843 if not checkForLastValidPosition(self.vehicleDirectionNode, false, 5) then
844 self:debugPrint("Stopping AI - No ground right (%.3f)", rightAreaPercentage)
845 return false
846 end
847 else
848 -- still check for last valid turn position since the distance to the field could be greater than 5m
849 checkForLastValidPosition(self.vehicleDirectionNode, false, 5)
850 end
851 end
852 end
853
854 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
855 log(" --(II)--> self.turnLeft:", self.turnLeft, "leftAreaPercentage:", leftAreaPercentage, "rightAreaPercentage:", rightAreaPercentage)
856 end
857
858 -- update turn data
859 driveStrategyStraight.turnLeft = not self.turnLeft
860 driveStrategyStraight:updateTurnData()
861 driveStrategyStraight.turnLeft = nil
862
863 --# finally set new AI direction and target before turn -> if turn gets interrupted the direction afterwards will still be ok
864 self.vehicle.aiDriveDirection[1], self.vehicle.aiDriveDirection[2] = -self.vehicle.aiDriveDirection[1], -self.vehicle.aiDriveDirection[2]
865
866 local sideOffset
867 if self.turnLeft then
868 sideOffset = turnData.sideOffsetLeft
869 else
870 sideOffset = turnData.sideOffsetRight
871 end
872
873 -- move the ai target by the work width to the turn direction
874 local x, z = self.vehicle.aiDriveTarget[1], self.vehicle.aiDriveTarget[2]
875 local dirX, dirZ = self.vehicle.aiDriveDirection[1], self.vehicle.aiDriveDirection[2]
876 local sideDistance = 2*sideOffset
877 local sideDirX, sideDirY = -dirZ, dirX
878 x, z = x+sideDirX*sideDistance, z+sideDirY*sideDistance
879
880 self.vehicle.aiDriveTarget[1], self.vehicle.aiDriveTarget[2] = x, z
881
882 self.vehicle:aiFieldWorkerStartTurn(self.turnLeft, self)
883
884 return true
885end

startTurnFinalization

Description
Definition
startTurnFinalization()
Code
921function AITurnStrategy:startTurnFinalization()
922 --# adapt segments to ground
923 for _,segment in pairs(self.turnSegments) do
924 if segment.startPoint ~= nil then
925 segment.startPoint[2] = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, segment.startPoint[1],0,segment.startPoint[3])
926 segment.endPoint[2] = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, segment.endPoint[1],0,segment.endPoint[3])
927 elseif segment.o ~= nil then
928 local x,y,z = getWorldTranslation(segment.o)
929 y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x,y,z)
930 setTranslation(segment.o ,x,y,z)
931 end
932 end
933
934 --# calc length of segments
935 for _,segment in pairs(self.turnSegments) do
936 if segment.startPoint ~= nil then
937 segment.length = MathUtil.vector3Length(segment.endPoint[1]-segment.startPoint[1], segment.endPoint[2]-segment.startPoint[2], segment.endPoint[3]-segment.startPoint[3])
938 self.turnSegmentsTotalLength = self.turnSegmentsTotalLength + segment.length
939 else
940 segment.length = math.rad(segment.endAngle - segment.startAngle) * segment.radius
941 self.turnSegmentsTotalLength = self.turnSegmentsTotalLength + segment.length
942 end
943 end
944
945end

update

Description
Definition
update()
Code
86function AITurnStrategy:update(dt)
87
88 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
89
90 AITurnStrategy.drawTurnSegments(self.turnSegments)
91
92 if self.maxTurningSizeBoxes ~= nil then
93 --if not self.isTurning then
94 if table.getn(self.maxTurningSizeBoxes) > 0 then
95 self.maxTurningSizeBoxes2 = {}
96 for _,box in pairs(self.maxTurningSizeBoxes) do
97 local box2 = {}
98 box2.points = {}
99 box2.color = {unpack(box.color)}
100
101 local x,y,z = box.x,box.y,box.z
102
103 local blx = box.xx*box.size[1] - box.zx*box.size[3]
104 local blz = box.xz*box.size[1] - box.zz*box.size[3]
105
106 local brx = -box.xx*box.size[1] - box.zx*box.size[3]
107 local brz = -box.xz*box.size[1] - box.zz*box.size[3]
108
109 local flx = box.xx*box.size[1] + box.zx*box.size[3]
110 local flz = box.xz*box.size[1] + box.zz*box.size[3]
111
112 local frx = -box.xx*box.size[1] + box.zx*box.size[3]
113 local frz = -box.xz*box.size[1] + box.zz*box.size[3]
114
115
116 table.insert(box2.points, { x+blx, y-box.size[2], z+blz } ) -- lower: lb
117 table.insert(box2.points, { x+brx, y-box.size[2], z+brz } ) -- rb
118 table.insert(box2.points, { x+frx, y-box.size[2], z+frz } ) -- rf
119 table.insert(box2.points, { x+flx, y-box.size[2], z+flz } ) -- lf
120
121 table.insert(box2.points, { x+blx, y+box.size[2], z+blz } ) -- upper: lb
122 table.insert(box2.points, { x+brx, y+box.size[2], z+brz } )
123 table.insert(box2.points, { x+frx, y+box.size[2], z+frz } )
124 table.insert(box2.points, { x+flx, y+box.size[2], z+flz } )
125
126 table.insert(box2.points, { x, y, z } )
127
128 table.insert(self.maxTurningSizeBoxes2, box2)
129 end
130 self.maxTurningSizeBoxes = {}
131 end
132
133 if self.maxTurningSizeBoxes2 ~= nil then
134 for _,box2 in pairs(self.maxTurningSizeBoxes2) do
135 local p = box2.points
136 local c = box2.color
137
138 -- bottom
139 drawDebugLine(p[1][1],p[1][2],p[1][3], c[1],c[2],c[3], p[2][1],p[2][2],p[2][3], c[1],c[2],c[3])
140 drawDebugLine(p[2][1],p[2][2],p[2][3], c[1],c[2],c[3], p[3][1],p[3][2],p[3][3], c[1],c[2],c[3])
141 drawDebugLine(p[3][1],p[3][2],p[3][3], c[1],c[2],c[3], p[4][1],p[4][2],p[4][3], c[1],c[2],c[3])
142 drawDebugLine(p[4][1],p[4][2],p[4][3], c[1],c[2],c[3], p[1][1],p[1][2],p[1][3], c[1],c[2],c[3])
143 -- top
144 drawDebugLine(p[5][1],p[5][2],p[5][3], c[1],c[2],c[3], p[6][1],p[6][2],p[6][3], c[1],c[2],c[3])
145 drawDebugLine(p[6][1],p[6][2],p[6][3], c[1],c[2],c[3], p[7][1],p[7][2],p[7][3], c[1],c[2],c[3])
146 drawDebugLine(p[7][1],p[7][2],p[7][3], c[1],c[2],c[3], p[8][1],p[8][2],p[8][3], c[1],c[2],c[3])
147 drawDebugLine(p[8][1],p[8][2],p[8][3], c[1],c[2],c[3], p[5][1],p[5][2],p[5][3], c[1],c[2],c[3])
148 -- left
149 drawDebugLine(p[1][1],p[1][2],p[1][3], c[1],c[2],c[3], p[5][1],p[5][2],p[5][3], c[1],c[2],c[3])
150 drawDebugLine(p[4][1],p[4][2],p[4][3], c[1],c[2],c[3], p[8][1],p[8][2],p[8][3], c[1],c[2],c[3])
151 -- right
152 drawDebugLine(p[2][1],p[2][2],p[2][3], c[1],c[2],c[3], p[6][1],p[6][2],p[6][3], c[1],c[2],c[3])
153 drawDebugLine(p[3][1],p[3][2],p[3][3], c[1],c[2],c[3], p[7][1],p[7][2],p[7][3], c[1],c[2],c[3])
154 -- center
155 p[9][2] = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, p[9][1],p[9][2],p[9][3]) + 2
156 drawDebugPoint(p[9][1],p[9][2],p[9][3], 1, 0, 0, 1)
157
158 Utils.renderTextAtWorldPosition(p[9][1],p[9][2],p[9][3], self.strategyName, getCorrectTextSize(0.012), 0)
159 end
160 end
161 end
162
163 end
164
165end

updateTurningSizeBox

Description
Definition
updateTurningSizeBox()
Code
463function AITurnStrategy:updateTurningSizeBox(box, turnLeft, turnData, distanceToTurn)
464 box.center[3] = distanceToTurn/2
465 box.size[1], box.size[2], box.size[3] = 3, 5, distanceToTurn/2
466end