LUADOC - Farming Simulator 22

Script v1_7_1_0

Engine v1_7_1_0

Foundation Reference

AIDriveStrategyStraight

Description
Class for a drive strategy just driving straight lines Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.
Parent
AIDriveStrategy
Functions

delete

Description
Definition
delete()
Code
23function AIDriveStrategyStraight:delete()
24 AIDriveStrategyStraight:superClass().delete(self)
25
26 for _, implement in ipairs(self.vehicle:getAttachedAIImplements()) do
27 implement.object:aiImplementEndLine()
28
29 local rootVehicle = implement.object.rootVehicle
30 rootVehicle:raiseStateChange(Vehicle.STATE_CHANGE_AI_END_LINE)
31 end
32
33 for _,strategy in pairs(self.turnStrategies) do
34 strategy:delete()
35 end
36end

getDriveData

Description
Definition
getDriveData()
Code
165function AIDriveStrategyStraight:getDriveData(dt, vX, vY, vZ)
166
167 -- during turn
168 if self.activeTurnStrategy ~= nil then
169 if not self.isTurning then
170 self.fieldEndGabDetected = false
171 self.fieldEndGabDetectedByBits = false
172 self.fieldEndGabLastPos = {}
173
174 self.lastValidTurnLeftPosition = {0,0,0}
175 self.lastValidTurnLeftValue = 0
176 self.lastValidTurnRightPosition = {0,0,0}
177 self.lastValidTurnRightValue = 0
178
179 self.gabAllowTurnLeft = true
180 self.gabAllowTurnRight = true
181 self.resetGabDetection = true
182
183 self.rowStartTranslation = nil
184 end
185
186 self.isTurning = true
187 self.isFirstRow = false
188
189 local tX, tZ, moveForwards, maxSpeed, distanceToStop = self.activeTurnStrategy:getDriveData(dt, vX,vY,vZ, self.turnData)
190 if tX ~= nil then
191 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
192 self.vehicle:addAIDebugText("===> distanceToStop = "..distanceToStop)
193 end
194 return tX, tZ, moveForwards, maxSpeed, distanceToStop
195 else
196 for _,turnStrategy in ipairs(self.turnStrategies) do
197 turnStrategy:onEndTurn(self.activeTurnStrategy.turnLeft)
198 end
199 self.turnLeft = self.activeTurnStrategy.turnLeft
200
201 self.activeTurnStrategy = nil
202 self.idealTurnStrategy = nil
203
204 -- ToDo: decide wether to measure again ?! (yes for plows, no for all other tools?)
205 self.turnDataIsStable = false
206 self.turnDataIsStableCounter = 0
207
208 self.lastLookAheadDistance = 5 -- 30
209 self.foundField = false
210 self.foundNoBetterTurnStrategy = false
211
212 self.lastHasNoField = false
213 end
214 else
215 self.isTurning = false
216 end
217
218 if self.rowStartTranslation == nil then
219 local x, y, z = getWorldTranslation(self.vehicle:getAIDirectionNode())
220 self.rowStartTranslation = {x, y, z}
221 end
222
223 --
224 local distanceToEndOfField, hasField, ownedField = self:getDistanceToEndOfField(dt, vX,vY,vZ)
225 local attachedAIImplements = self.vehicle:getAttachedAIImplements()
226
227 if hasField and distanceToEndOfField > 0 then
228 self.foundField = true
229 end
230
231 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
232 self.vehicle:addAIDebugText(string.format("==(I)=> distanceToEndOfField: %.1f", distanceToEndOfField))
233 end
234
235 -- this is the case when turn is finished and tool is too far away from field
236 if self.foundField == false and distanceToEndOfField <= 0 and self.turnLeft ~= nil then
237 local _,_,lz = worldToLocal(self.vehicle:getAIDirectionNode(), self.vehicle.aiDriveTarget[1], 0, self.vehicle.aiDriveTarget[2])
238 if lz > 0 then
239 distanceToEndOfField = self.lookAheadDistanceField
240 self.lastHasNoField = false
241 end
242 end
243
244 if not hasField and self.foundField ~= true and self.turnLeft == nil then
245 if ownedField then
246 self.vehicle:stopCurrentAIJob(AIMessageErrorNoFieldFound.new())
247 self:debugPrint("Stopping AIVehicle - unable to find field")
248 else
249 self.vehicle:stopCurrentAIJob(AIMessageErrorFieldNotOwned.new())
250 self:debugPrint("Stopping AIVehicle - field not owned")
251 end
252
253 return nil
254 end
255
256 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
257 self.vehicle:addAIDebugText(string.format("==(II)=> distanceToEndOfField: %.1f", distanceToEndOfField))
258 self.vehicle:addAIDebugText(string.format("===> foundField: %s", tostring(self.foundField)))
259 self.vehicle:addAIDebugText(string.format("===> lastHasNoField: %s", tostring(self.lastHasNoField)))
260 if self.turnData ~= nil then
261 self.vehicle:addAIDebugText(string.format("===> useExtraStraight: %s %s", tostring(self.turnData.useExtraStraightLeft), tostring(self.turnData.useExtraStraightRight)))
262 end
263 end
264
265 -- if the field border is found in front of us we continue to drive straight until we reach the field border on the side we turn (or on both sides if we have not decided the direction yet)
266 -- this helps us to complete fields with a 45 degress field border to 100%
267 if distanceToEndOfField <= 0 then
268 for _, implement in ipairs(attachedAIImplements) do
269 local leftMarker, rightMarker, _ = implement.object:getAIMarkers()
270 local hasNoFullCoverageArea, _ = implement.object:getAIHasNoFullCoverageArea()
271
272 local allowCheck = self.turnLeft == nil or (self.turnLeft and self.gabAllowTurnRight) or (self.turnLeft == false and self.gabAllowTurnLeft)
273 allowCheck = allowCheck and not hasNoFullCoverageArea
274 allowCheck = allowCheck and not self.fieldEndGabDetectedByBits
275
276 if allowCheck then
277 local dir = self.turnLeft and -1 or 1
278 local width = calcDistanceFrom(leftMarker, rightMarker)
279 local sX, sZ, wX, wZ, hX, hZ = AIVehicleUtil.getAreaDimensions(self.vehicle.aiDriveDirection[1], self.vehicle.aiDriveDirection[2], leftMarker, rightMarker, dir*width, 0, 1, false)
280 local area, _ = AIVehicleUtil.getAIAreaOfVehicle(implement.object, sX,sZ, wX,wZ, hX,hZ, false)
281 if area > 0 then
282 if not AIVehicleUtil.getIsAreaOwned(self.vehicle, sX, sZ, wX, wZ, hX, hZ) then
283 area = 0
284 end
285 end
286
287 if self.turnLeft == nil then
288 if not self.gabAllowTurnLeft then
289 area = 0
290 end
291
292 if area <= 0 then
293 if self.gabAllowTurnRight then
294 dir = -dir
295 sX, sZ, wX, wZ, hX, hZ = AIVehicleUtil.getAreaDimensions(self.vehicle.aiDriveDirection[1], self.vehicle.aiDriveDirection[2], leftMarker, rightMarker, dir*width, 0, 1, false)
296 area, _ = AIVehicleUtil.getAIAreaOfVehicle(implement.object, sX,sZ, wX,wZ, hX,hZ, false)
297
298 if area > 0 then
299 if not AIVehicleUtil.getIsAreaOwned(self.vehicle, sX, sZ, wX, wZ, hX, hZ) then
300 area = 0
301 end
302 end
303 end
304 end
305 end
306
307 if area > 0 then
308 distanceToEndOfField = 5
309 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
310 self.vehicle:addAIDebugText(string.format("===> continue until field border on left/right (area: %d)", area))
311 end
312 self.driveExtraDistanceToFieldBorder = true
313 end
314 end
315 end
316 else
317 self.driveExtraDistanceToFieldBorder = false
318 end
319
320 local lookAheadDistance = self.lastLookAheadDistance
321
322 local distanceToCollision = 0
323
324 if distanceToEndOfField > 0 and not self.useCorridor then
325 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
326 self.vehicle:addAIDebugText(string.format(" turnDataIsStable: %s | %d", tostring(self.turnDataIsStable), self.turnDataIsStableCounter))
327
328 if self.turnData ~= nil then
329 self.vehicle:addAIDebugText(string.format(" turn radius: %.2f", self.turnData.radius or 0))
330 end
331 end
332 if not self.turnDataIsStable then
333 self:updateTurnData()
334 end
335
336 local searchForTurnStrategy = self.idealTurnStrategy == nil
337
338 if self.idealTurnStrategy ~= nil then
339 distanceToCollision = self.idealTurnStrategy:getDistanceToCollision(dt, vX,vY,vZ, self.turnData, lookAheadDistance)
340 if distanceToCollision < lookAheadDistance then
341 searchForTurnStrategy = true
342 else
343 self.foundNoBetterTurnStrategy = false
344 end
345 end
346
347 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
348 self.vehicle:addAIDebugText(string.format(" searchForTurnStrategy: %s", tostring(searchForTurnStrategy)))
349 end
350
351 if searchForTurnStrategy and self.foundNoBetterTurnStrategy ~= true then
352 for i,turnStrategy in ipairs(self.turnStrategies) do
353 if turnStrategy ~= self.idealTurnStrategy then
354 local colDist = turnStrategy:getDistanceToCollision(dt, vX,vY,vZ, self.turnData, lookAheadDistance)
355
356 if colDist >= lookAheadDistance and not turnStrategy.collisionDetected then
357 self.idealTurnStrategy = turnStrategy
358 distanceToCollision = colDist
359 break
360 end
361 end
362 end
363 end
364
365 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
366 self.vehicle:addAIDebugText(string.format("===> distanceToCollision: %.1f", distanceToCollision))
367 end
368
369 if self.idealTurnStrategy ~= nil then
370 -- reset valid position of opposite turn side since there is a reason why not to turn into this direction
371 if self.idealTurnStrategy.turnLeft ~= self.turnLeft then
372 if self.idealTurnStrategy.turnLeft then
373 self.lastValidTurnRightPosition[1] = 0
374 self.lastValidTurnRightPosition[2] = 0
375 self.lastValidTurnRightPosition[3] = 0
376 self.lastValidTurnRightValue = 0
377 else
378 self.lastValidTurnLeftPosition[1] = 0
379 self.lastValidTurnLeftPosition[2] = 0
380 self.lastValidTurnLeftPosition[3] = 0
381 self.lastValidTurnLeftValue = 0
382 end
383 end
384
385 self.turnLeft = self.idealTurnStrategy.turnLeft
386 end
387 end
388
389 local distanceToTurn = math.min(distanceToEndOfField, distanceToCollision)
390 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
391 self.vehicle:addAIDebugText(string.format("===> Distance to turn: %.1f", distanceToTurn))
392 self.vehicle:addAIDebugText(string.format("===> turnLeft: %s", tostring(self.turnLeft)))
393 end
394
395 if distanceToCollision < lookAheadDistance and distanceToCollision < distanceToEndOfField then
396 if self.turnLeft ~= nil or self.idealTurnStrategy == nil then
397 AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, self.vehicle)
398 for _,implement in pairs(attachedAIImplements) do
399 AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, implement.object)
400 end
401
402 local leftAreaPercentage, rightAreaPercentage = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle, self.turnData)
403
404 if (self.turnLeft and rightAreaPercentage < AIVehicleUtil.VALID_AREA_THRESHOLD) or (not self.turnLeft and leftAreaPercentage < AIVehicleUtil.VALID_AREA_THRESHOLD) or self.idealTurnStrategy == nil then
405 self.foundNoBetterTurnStrategy = false
406
407 local collision = self.turnStrategies[1]:checkCollisionInFront(self.turnData)
408 if collision or distanceToEndOfField <= 0 then
409 self.foundNoBetterTurnStrategy = false
410 self.idealTurnStrategy = nil
411
412 if self.turnLeft == nil then
413 self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new())
414 self:debugPrint("Stopping AIVehicle - turn direction undefined")
415 return nil
416 end
417
418 else
419 distanceToTurn = lookAheadDistance
420 end
421 end
422
423 end
424 end
425
426 -- if we collide to the left or to the right and we still got some area to work in front of us
427 -- we check if there is no collision and still valid ground in the front and continue to work
428 -- as soon as we reach the field end or a collision we will start to reverse and turn in front of the collision
429 if self.allowTurnBackward or self.aiToolReverserDirectionNode ~= nil then
430 if distanceToTurn <= 0 and distanceToEndOfField > 0 then
431 local collision = self.turnStrategies[1]:checkCollisionInFront(self.turnData, 0)
432
433 -- if the vehicle is somehow blocked and can not move (slopes etc.) we cancel the corridor and start to reverse
434 if not collision then
435 if self.vehicle:getLastSpeed() < 1.5 then
436 self.useCorridorTimeOut = self.useCorridorTimeOut + dt
437 else
438 self.useCorridorTimeOut = 0
439 end
440
441 if self.useCorridorTimeOut > 3000 then
442 collision = true
443 end
444 end
445
446 if not collision then
447 distanceToTurn = distanceToEndOfField
448 self.useCorridor = true
449 if self.useCorridorStart == nil then
450 self.useCorridorStart = {vX, vY, vZ}
451 else
452 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
453 local distance = MathUtil.vector3Length(self.useCorridorStart[1]-vX, self.useCorridorStart[2]-vY,self.useCorridorStart[3]-vZ)
454 self.vehicle:addAIDebugText(string.format("===> is using a corridor (%.1fm)", distance))
455 end
456 end
457 else
458 self.useCorridor = false
459 end
460 else
461 self.useCorridor = false
462 end
463 end
464
465 if distanceToTurn <= 0 and not self.useCorridor then
466 -- call end line for all tools that were still 'on field'
467 for _, implement in ipairs(attachedAIImplements) do
468 if implement.aiEndLineCalled == nil or not implement.aiEndLineCalled then
469 implement.aiEndLineCalled = true
470 implement.aiStartLineCalled = nil
471
472 implement.object:aiImplementEndLine()
473
474 local rootVehicle = implement.object.rootVehicle
475 rootVehicle:raiseStateChange(Vehicle.STATE_CHANGE_AI_END_LINE)
476 end
477 end
478
479 self.lastHasNoField = false -- reset state for end of field check
480
481 self.activeTurnStrategy = self.idealTurnStrategy
482 if self.turnData ~= nil and self.activeTurnStrategy ~= nil then
483 if self.useCorridorStart ~= nil then
484 local distance = MathUtil.vector3Length(self.useCorridorStart[1]-vX, self.useCorridorStart[2]-vY,self.useCorridorStart[3]-vZ)
485 self.corridorDistance = distance
486 self.useCorridorStart = nil
487 self:debugPrint(string.format("start turn with corridor offset: %.2f", distance))
488 end
489 local canTurn = self.activeTurnStrategy:startTurn(self)
490 self.activeTurnStrategy.lastValidTurnPositionOffset = 0
491 self.corridorDistance = 0
492 if not canTurn then
493 self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new())
494 self:debugPrint("Stopping AIVehicle - could not start to turn (%s)", self.activeTurnStrategy.strategyName)
495
496 return nil
497 end
498 return self.activeTurnStrategy:getDriveData(dt, vX,vY,vZ, self.turnData)
499 else
500 self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new())
501 self:debugPrint("Stopping AIVehicle - no turn data found")
502 end
503 return nil
504 else
505 self.vehicle:addAIDebugText("===> Drive straight")
506 return self:getDriveStraightData(dt, vX,vY,vZ, distanceToTurn, distanceToEndOfField)
507 end
508
509end

getDriveStraightData

Description
Definition
getDriveStraightData()
Code
513function AIDriveStrategyStraight:getDriveStraightData(dt, vX, vY, vZ, distanceToTurn, distanceToEndOfField)
514 if self.vehicle.aiDriveDirection == nil then
515 return nil, nil, true, 0, 0
516 end
517
518 -- drive along the desired direction
519 local pX, pZ = MathUtil.projectOnLine(vX, vZ, self.vehicle.aiDriveTarget[1], self.vehicle.aiDriveTarget[2], self.vehicle.aiDriveDirection[1], self.vehicle.aiDriveDirection[2])
520 local tX = pX + self.vehicle.aiDriveDirection[1] * self.vehicle.maxTurningRadius
521 local tZ = pZ + self.vehicle.aiDriveDirection[2] * self.vehicle.maxTurningRadius
522
523 local maxSpeed = self.vehicle:getSpeedLimit(true)
524 local attachedAIImplements = self.vehicle:getAttachedAIImplements()
525
526
527 -- 1: raise, nil: stay, -1: lower
528 for i=#self.toolLineStates, 1, -1 do
529 self.toolLineStates[i] = nil
530 end
531
532
533 local nrOfImplements = #attachedAIImplements
534 for i=nrOfImplements,1,-1 do
535 local implement = attachedAIImplements[i]
536
537 if self.toolLineStates[i+1] == -1 and attachedAIImplements[i+1].object:getAttacherVehicle() == implement.object then
538 self.toolLineStates[i] = -1
539 else
540 local leftMarker, rightMarker, backMarker, markersInverted = implement.object:getAIMarkers()
541 -- we apply a savety offset here cause on same implements the markers are moved on x axis while lifting the tool
542 -- with this offset we don't check the next row
543 local safetyOffset = 0.2
544
545 local markerZOffset = 0
546 local _, _, areaLength = localToLocal(backMarker, leftMarker, 0,0,0)
547 local size = 1
548 local doAdditionalFieldEndChecks = false
549 local hasNoFullCoverageArea, hasNoFullCoverageAreaOffset = implement.object:getAIHasNoFullCoverageArea()
550 if hasNoFullCoverageArea then
551 markerZOffset = areaLength
552 size = math.abs(markerZOffset) + hasNoFullCoverageAreaOffset
553 doAdditionalFieldEndChecks = true
554 end
555
556 local getAreaDimensions = function(leftNode, rightNode, xOffset, zOffset, areaSize, invertXOffset)
557 local xOffsetLeft, xOffsetRight = xOffset, xOffset
558 if invertXOffset == nil or invertXOffset then
559 xOffsetLeft = -xOffsetLeft
560 end
561
562 if markersInverted then
563 xOffsetLeft = -xOffsetLeft
564 xOffsetRight = -xOffsetRight
565 end
566
567 local lX, _, lZ = localToWorld(leftNode, xOffsetLeft, 0, zOffset)
568 local rX, _, rZ = localToWorld(rightNode, xOffsetRight, 0, zOffset)
569
570 local sX = lX - (0.5 * self.vehicle.aiDriveDirection[1])
571 local sZ = lZ - (0.5 * self.vehicle.aiDriveDirection[2])
572 local wX = rX - (0.5 * self.vehicle.aiDriveDirection[1])
573 local wZ = rZ - (0.5 * self.vehicle.aiDriveDirection[2])
574 local hX = lX + (areaSize * self.vehicle.aiDriveDirection[1])
575 local hZ = lZ + (areaSize * self.vehicle.aiDriveDirection[2])
576
577 return sX, sZ, wX, wZ, hX, hZ
578 end
579
580 local sX, sZ, wX, wZ, hX, hZ = getAreaDimensions(leftMarker, rightMarker, safetyOffset, markerZOffset, size)
581
582 local area, totalArea = AIVehicleUtil.getAIAreaOfVehicle(implement.object, sX,sZ, wX,wZ, hX,hZ, false)
583 if area / totalArea > 0.01 then
584 if not self.fieldEndGabDetected then
585 self.toolLineStates[i] = -1
586 end
587
588 -- detect gab at the end of the field to another field
589 -- needed since the plow check area is as long as the plow is and this length can be longer than the distance between fields
590 -- so if the back of the plow is on a field, the center not and the front also on a field we reached the headland
591 if doAdditionalFieldEndChecks then
592 local distance1 = 0
593 local distance2 = 0
594
595 local sX1, sZ1, wX1, wZ1, hX1, hZ1 = getAreaDimensions(leftMarker, rightMarker, safetyOffset, 1, 1)
596 local area2, _ = AIVehicleUtil.getAIAreaOfVehicle(implement.object, sX1,sZ1, wX1,wZ1, hX1,hZ1, false)
597 if #self.fieldEndGabLastPos > 0 then
598 distance1 = math.abs(MathUtil.vector2Length(sX1-self.fieldEndGabLastPos[1], sZ1-self.fieldEndGabLastPos[2]))
599 end
600
601 local sX2, sZ2, wX2, wZ2, hX2, hZ2 = getAreaDimensions(leftMarker, rightMarker, safetyOffset, -1, 1)
602 local area3, _ = AIVehicleUtil.getAIAreaOfVehicle(implement.object, sX2,sZ2, wX2,wZ2, hX2,hZ2, false)
603 if #self.fieldEndGabLastPos > 0 then
604 distance2 = math.abs(MathUtil.vector2Length(sX2-self.fieldEndGabLastPos[1], sZ2-self.fieldEndGabLastPos[2]))
605 end
606
607 if area3 == 0 and area2 > 0 and distance1 > 0 and distance2 > 0 then
608 if distance1 > 3 then
609 if distance1 > distance2 then
610 self.fieldEndGabDetected = true
611 self.toolLineStates[i] = 1
612 end
613 end
614 end
615
616 if area2 > 0 then
617 self.fieldEndGabLastPos[1] = sX1
618 self.fieldEndGabLastPos[2] = sZ1
619 end
620 end
621
622 -- check the left and right side of the tool for valid ground
623 -- in case we got a "L" field we save the last valid position and reverse to that position if we reach the headland but don't have valid ground
624 if not self.driveExtraDistanceToFieldBorder then
625 local usedAreaLength = math.abs(areaLength)
626 if markerZOffset ~= 0 then
627 usedAreaLength = 0
628 end
629
630 local dir = self.turnLeft and -1 or 1
631 local width = calcDistanceFrom(leftMarker, rightMarker)
632 sX, sZ, wX, wZ, hX, hZ = getAreaDimensions(leftMarker, rightMarker, dir*width, markerZOffset-usedAreaLength, size, false)
633 area, _ = AIVehicleUtil.getAIAreaOfVehicle(implement.object, sX,sZ, wX,wZ, hX,hZ, false)
634 local x, y, z = 0, 0, 0
635
636 if not AIVehicleUtil.getIsAreaOwned(self.vehicle, sX, sZ, wX, wZ, hX, hZ) then
637 area = 0
638 end
639
640 if area > 0 then
641 x, y, z = getWorldTranslation(self.vehicle:getAIDirectionNode())
642 end
643
644 -- eather check the other side if we found nothing
645 -- or if we don't have a turn direction set yet so we check in both directions to be able to turn in both
646 if area == 0 or self.turnLeft == nil then
647 sX, sZ, wX, wZ, hX, hZ = getAreaDimensions(leftMarker, rightMarker, -dir*width, markerZOffset-usedAreaLength, size, false)
648 local areaOpp, _ = AIVehicleUtil.getAIAreaOfVehicle(implement.object, sX,sZ, wX,wZ, hX,hZ, false)
649
650 if not AIVehicleUtil.getIsAreaOwned(self.vehicle, sX, sZ, wX, wZ, hX, hZ) then
651 areaOpp = 0
652 end
653
654 if areaOpp > 0 then
655 x, y, z = getWorldTranslation(self.vehicle:getAIDirectionNode())
656 end
657
658 if self.turnLeft ~= nil then
659 area = areaOpp
660 dir = -dir
661 else
662 -- if we don't have a turn direction set yet we check in both directions for valid ground and set the lastValidPosition on both sides
663 if area > 0 then
664 self.lastValidTurnLeftPosition[1], self.lastValidTurnLeftPosition[2], self.lastValidTurnLeftPosition[3] = x, y, z
665 self.lastValidTurnLeftValue = math.max(area, self.lastValidTurnLeftValue)
666 end
667
668 if areaOpp > 0 then
669 self.lastValidTurnRightPosition[1], self.lastValidTurnRightPosition[2], self.lastValidTurnRightPosition[3] = x, y, z
670 self.lastValidTurnRightValue = math.max(areaOpp, self.lastValidTurnRightValue)
671 end
672 end
673 end
674
675 if self.turnLeft ~= nil then
676 if area > 0 then
677 if dir > 0 then
678 if area > self.lastValidTurnRightValue then
679 self.lastValidTurnLeftPosition[1], self.lastValidTurnLeftPosition[2], self.lastValidTurnLeftPosition[3] = x, y, z
680 self.lastValidTurnLeftValue = math.max(area, self.lastValidTurnLeftValue)
681 self.lastValidTurnRightPosition[1], self.lastValidTurnRightPosition[2], self.lastValidTurnRightPosition[3] = 0, 0, 0
682 end
683 else
684 if area > self.lastValidTurnLeftValue then
685 self.lastValidTurnRightPosition[1], self.lastValidTurnRightPosition[2], self.lastValidTurnRightPosition[3] = x, y, z
686 self.lastValidTurnRightValue = math.max(area, self.lastValidTurnRightValue)
687 self.lastValidTurnLeftPosition[1], self.lastValidTurnLeftPosition[2], self.lastValidTurnLeftPosition[3] = 0, 0, 0
688 end
689 end
690 end
691 end
692
693 self.lastValidTurnCheckPosition[1], self.lastValidTurnCheckPosition[2], self.lastValidTurnCheckPosition[3] = getWorldTranslation(self.vehicle:getAIDirectionNode())
694
695 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
696 DebugUtil.drawDebugGizmoAtWorldPos(self.lastValidTurnLeftPosition[1], self.lastValidTurnLeftPosition[2], self.lastValidTurnLeftPosition[3], 0, 1, 0, 0, 1, 0, "last valid left", true)
697 DebugUtil.drawDebugGizmoAtWorldPos(self.lastValidTurnRightPosition[1], self.lastValidTurnRightPosition[2], self.lastValidTurnRightPosition[3], 0, 1, 0, 0, 1, 0, "last valid right", true)
698 end
699 end
700 else
701 -- If we don't need to lower, check if we need to raise it, either not on field or nothing close by (check slightly further ahead than for lowering)
702 if self.lastHasNoField then
703 self.toolLineStates[i] = 1
704 else
705 local lX, _, lZ = localToWorld(leftMarker, -safetyOffset, 0, markerZOffset)
706 local hX2 = lX + (math.max(distanceToTurn, 2.5) * self.vehicle.aiDriveDirection[1])
707 local hZ2 = lZ + (math.max(distanceToTurn, 2.5) * self.vehicle.aiDriveDirection[2])
708
709 local area2, _ = AIVehicleUtil.getAIAreaOfVehicle(implement.object, sX,sZ, wX,wZ, hX2,hZ2, false)
710 if area2 <= 0 then
711 self.toolLineStates[i] = 1
712 end
713 end
714 end
715 end
716 end
717
718 for i=nrOfImplements,1,-1 do
719 local implement = attachedAIImplements[i]
720
721 if implement.aiLastStateChangeDistance == nil then
722 implement.aiLastStateChangeDistance = math.huge
723 end
724
725 -- allow state changes only after the vehicle has been moved at least 25cm
726 -- this helps us if the area position slightly changes while lowering/lifting
727 -- and the result of the ai area checks is then different -> this can result in an endless lower/lift cycle [Bug #32754]
728 implement.aiLastStateChangeDistance = implement.aiLastStateChangeDistance + implement.object.lastMovedDistance
729 if implement.aiLastStateChangeDistance > 0.25 then
730 if self.toolLineStates[i] == -1 then
731 if implement.aiStartLineCalled == nil or not implement.aiStartLineCalled then
732 implement.aiStartLineCalled = true
733 implement.aiEndLineCalled = nil
734
735 implement.object:aiImplementStartLine()
736 implement.aiLastStateChangeDistance = 0
737
738 local rootVehicle = implement.object:getRootVehicle()
739 rootVehicle:raiseStateChange(Vehicle.STATE_CHANGE_AI_START_LINE)
740 end
741 elseif self.toolLineStates[i] == 1 then
742 if implement.aiEndLineCalled == nil or not implement.aiEndLineCalled then
743 implement.aiEndLineCalled = true
744 implement.aiStartLineCalled = nil
745
746 implement.object:aiImplementEndLine()
747 implement.aiLastStateChangeDistance = 0
748
749 local rootVehicle = implement.object:getRootVehicle()
750 rootVehicle:raiseStateChange(Vehicle.STATE_CHANGE_AI_END_LINE)
751 end
752 end
753 end
754 end
755
756 -- while the attached tools are only partly lowered we limit the speed to 10kph
757 -- this gives more time to detect the area and break early enough
758 local anyToolLowered = false
759 local anyToolLifted = false
760 for i=1, #attachedAIImplements do
761 local implement = attachedAIImplements[i]
762 if implement.aiStartLineCalled then
763 anyToolLowered = true
764 elseif implement.aiEndLineCalled then
765 anyToolLifted = true
766 end
767 end
768 if anyToolLowered and anyToolLifted then
769 maxSpeed = math.min(maxSpeed, 10)
770 self.vehicle:addAIDebugText(string.format("===> Tools only partly lowered, limit speed to 10kph"))
771 end
772
773 local canContinueWork, stopAI, stopReason = self.vehicle:getCanAIFieldWorkerContinueWork()
774 if not canContinueWork then
775 maxSpeed = 0
776
777 if stopAI then
778 self.vehicle:stopCurrentAIJob(stopReason or AIMessageErrorUnknown.new())
779 self:debugPrint("Stopping AIVehicle - cannot continue work")
780 end
781 end
782
783 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
784 self.vehicle:addAIDebugText(string.format("===> canContinueWork: %s", tostring(canContinueWork)))
785 self.vehicle:addAIDebugLine({vX,vY,vZ}, {tX,vY,tZ}, {1,1,1})
786 end
787
788 --detect gabs to other fields
789 if canContinueWork and self.vehicle:getLastSpeed() > 1 and self.vehicle.movingDirection > 0 then
790 self.gabAllowTurnLeft = true
791 self.gabAllowTurnRight = true
792 local changeCounter = 0
793 local lastBit = false
794 local gabBits = ""
795 local gabPos = -1
796 local fieldEndBits = ""
797 for _, implement in ipairs(attachedAIImplements) do
798 if implement.aiStartLineCalled then
799 local hasNoFullCoverageArea, _ = implement.object:getAIHasNoFullCoverageArea()
800 local leftMarker, rightMarker, _, markersInverted = implement.object:getAIMarkers()
801 local markerDir = markersInverted and -1 or 1
802
803 local width = calcDistanceFrom(leftMarker, rightMarker) + 0.8
804 local divisions = 2.5
805 if width < 8.5 then
806 divisions = 1.5
807 end
808 if width < 4.5 then
809 divisions = 1
810 end
811
812 local checkpoints = implement.object.aiImplementGabCheckpoints or (MathUtil.round(width / divisions, 0) + 1)
813 if implement.object.aiImplementGabCheckpoints == nil then
814 implement.object.aiImplementGabCheckpoints = checkpoints
815 end
816 if implement.object.aiImplementGabCheckpointValues == nil or implement.object.aiImplementFieldEndCheckpointValues == nil or self.resetGabDetection then
817 implement.object.aiImplementGabCheckpointValues = {}
818 implement.object.aiImplementFieldEndCheckpointValues = {}
819 end
820 local values = implement.object.aiImplementGabCheckpointValues
821 local valuesFieldEnd = implement.object.aiImplementFieldEndCheckpointValues
822
823 implement.object.aiImplementCurCheckpoint = (implement.object.aiImplementCurCheckpoint or -1) + 1
824 if implement.object.aiImplementCurCheckpoint >= checkpoints then
825 implement.object.aiImplementCurCheckpoint = 0
826 end
827 local currentCheckpoint = implement.object.aiImplementCurCheckpoint
828
829
830 if checkpoints > 2 then
831 local checkpointWidth = width / (checkpoints - 1)
832 local valueIndex = currentCheckpoint + 1
833
834 local x1, y1, z1 = localToWorld(leftMarker, 0.4 * markerDir, 0, 0)
835 local x2, y2, z2 = localToWorld(rightMarker, -0.4 * markerDir, 0, 0)
836
837 local x = x1 - ((x1 - x2) * ((currentCheckpoint*checkpointWidth) / width))
838 local y = y1 - ((y1 - y2) * ((currentCheckpoint*checkpointWidth) / width))
839 local z = z1 - ((z1 - z2) * ((currentCheckpoint*checkpointWidth) / width))
840
841 local isOnField, _ = FSDensityMapUtil.getFieldDataAtWorldPosition(x, y, z)
842 local bit = values[valueIndex]
843 bit = bit or isOnField
844
845 if hasNoFullCoverageArea then
846 if valuesFieldEnd[valueIndex] == 2 and isOnField then
847 valuesFieldEnd[valueIndex] = 3
848 end
849 end
850
851 if valuesFieldEnd[valueIndex] == 1 and not isOnField then
852 valuesFieldEnd[valueIndex] = 2
853 end
854
855 if valuesFieldEnd[valueIndex] == nil and isOnField then
856 local allowed = true
857 for i=1, checkpoints do
858 if valuesFieldEnd[i] ~= nil and valuesFieldEnd[i] >= 2 then
859 allowed = false
860 break
861 end
862 end
863
864 if allowed then
865 valuesFieldEnd[valueIndex] = 1
866 end
867 end
868
869 values[valueIndex] = bit
870
871 for i=1, checkpoints do
872 if values[i] ~= lastBit then
873 changeCounter = changeCounter + 1
874 if changeCounter > 2 then
875 gabPos = (i-1) / checkpoints
876 end
877 lastBit = values[i]
878 end
879
880 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
881 gabBits = gabBits .. tostring((values[i] and 1) or 0)
882 fieldEndBits = fieldEndBits .. tostring((valuesFieldEnd[i] == 3 and "-") or (valuesFieldEnd[i] == nil and "?" or (valuesFieldEnd[i] == 1 and "O" or "_")))
883 end
884 end
885
886 if self.isFirstRow then
887 local hasLeftGab = gabPos > 0 and gabPos < 0.5
888 local hasRightGab = gabPos >= 0.5
889 self.gabAllowTurnLeft = self.gabAllowTurnLeft and values[1] and not hasLeftGab
890 self.gabAllowTurnRight = self.gabAllowTurnRight and values[#values] and not hasRightGab
891 else
892 -- if we haved turned once and have a fixed driving direction we do not allow any gab
893 -- do not use self.turnLeft ~= nil here to check since the turn direction could already be fixed in the first row (due to collisions etc.)
894 self.gabAllowTurnLeft = self.gabAllowTurnLeft and values[1] and gabPos < 0
895 self.gabAllowTurnRight = self.gabAllowTurnRight and values[#values] and gabPos < 0
896 end
897 end
898
899 local hasHadFieldContact = false
900 for i=1, checkpoints do
901 if valuesFieldEnd[i] ~= nil then
902 hasHadFieldContact = true
903 break
904 end
905 end
906
907 local allOutOfField = true
908 for i=1, checkpoints do
909 if valuesFieldEnd[i] == 1 then
910 allOutOfField = false
911 break
912 end
913 end
914
915 -- tools with no full coverage area check when one of the checks enters a new field since the full work area needs to be out of the field
916 -- for the others we check if all checks have just left the field
917 local reEnteringField = false
918 if hasNoFullCoverageArea then
919 for i=1, checkpoints do
920 if valuesFieldEnd[i] == 3 then
921 reEnteringField = true
922 break
923 end
924 end
925 else
926 reEnteringField = true
927 end
928
929 self.fieldEndGabDetectedByBits = checkpoints > 0 and hasHadFieldContact and allOutOfField and reEnteringField
930 else
931 -- still make use of resetGabDetection variable while not active
932 if implement.object.aiImplementGabCheckpointValues == nil or implement.object.aiImplementFieldEndCheckpointValues == nil or self.resetGabDetection then
933 implement.object.aiImplementGabCheckpointValues = {}
934 implement.object.aiImplementFieldEndCheckpointValues = {}
935 end
936 end
937 end
938
939 if self.resetGabDetection then
940 self.resetGabDetection = false
941 end
942
943 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
944 self.vehicle:addAIDebugText(string.format("===> gab bits: %s", gabBits))
945 if gabPos > 0 then
946 self.vehicle:addAIDebugText(string.format("===> gab pos: %s%% side: %s", gabPos*100, (gabPos < 0.5 and "left") or "right"))
947 end
948 self.vehicle:addAIDebugText(string.format("===> gab allow Left: %s", self.gabAllowTurnLeft))
949 self.vehicle:addAIDebugText(string.format("===> gab allow right: %s", self.gabAllowTurnRight))
950 self.vehicle:addAIDebugText(string.format("===> field end detection: %s (%s)", fieldEndBits, self.fieldEndGabDetectedByBits))
951 end
952 end
953
954 return tX, tZ, true, maxSpeed, distanceToTurn
955end

new

Description
Definition
new()
Code
11function AIDriveStrategyStraight.new(customMt)
12 if customMt == nil then
13 customMt = AIDriveStrategyStraight_mt
14 end
15
16 local self = AIDriveStrategy.new(customMt)
17
18 return self
19end

setAIVehicle

Description
Definition
setAIVehicle()
Code
40function AIDriveStrategyStraight:setAIVehicle(vehicle)
41 AIDriveStrategyStraight:superClass().setAIVehicle(self, vehicle)
42
43 --# set AI direction acccording to current orientation
44 local dx,_,dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1)
45 if g_currentMission.snapAIDirection then
46 local snapAngle = self.vehicle:getDirectionSnapAngle()
47 local terrainAngle = math.pi / math.max(g_currentMission.fieldGroundSystem:getGroundAngleMaxValue() + 1, 4) -- snap at least in 45deg angles
48 snapAngle = math.max(snapAngle, terrainAngle)
49
50 local angleRad = MathUtil.getYRotationFromDirection(dx, dz)
51 angleRad = math.floor(angleRad / snapAngle + 0.5) * snapAngle
52
53 dx, dz = MathUtil.getDirectionFromYRotation(angleRad)
54 else
55 local length = MathUtil.vector2Length(dx,dz)
56 dx = dx / length
57 dz = dz / length
58 end
59 self.vehicle.aiDriveDirection = {dx, dz}
60
61 local x,_,z = getWorldTranslation(self.vehicle:getAIDirectionNode())
62 self.vehicle.aiDriveTarget = {x, z}
63
64 --# create turn strategy
65 local useDefault = true
66
67 -- check if all attached implements allow turning backward
68 self.allowTurnBackward = AIVehicleUtil.getAttachedImplementsAllowTurnBackward(vehicle)
69
70 if not self.allowTurnBackward then
71 useDefault = false
72 end
73
74 for _, implement in ipairs(self.vehicle:getAttachedAIImplements()) do
75 implement.aiLastStateChangeDistance = nil
76 end
77
78 self.aiToolReverserDirectionNode = AIVehicleUtil.getAIToolReverserDirectionNode(self.vehicle)
79 self.vehicleAIReverserNode = self.vehicle:getAIReverserNode()
80
81 self.turnStrategies = {}
82
83 local usedStrategies = ''
84
85 if useDefault then
86 usedStrategies = usedStrategies .. " +DEFAULT "
87 table.insert(self.turnStrategies, AITurnStrategyDefault.new())
88
89 usedStrategies = usedStrategies .. " +DEFAULT (reverse)"
90 table.insert(self.turnStrategies, AITurnStrategyDefaultReverse.new())
91 end
92
93 if self.aiToolReverserDirectionNode ~= nil or useDefault or self.vehicleAIReverserNode then
94 usedStrategies = usedStrategies .. " +BULBs (reverse)"
95 table.insert(self.turnStrategies, AITurnStrategyBulb1Reverse.new())
96 table.insert(self.turnStrategies, AITurnStrategyBulb2Reverse.new())
97 table.insert(self.turnStrategies, AITurnStrategyBulb3Reverse.new())
98 else
99 usedStrategies = usedStrategies .. " +BULBs"
100 table.insert(self.turnStrategies, AITurnStrategyBulb1.new())
101 table.insert(self.turnStrategies, AITurnStrategyBulb2.new())
102 table.insert(self.turnStrategies, AITurnStrategyBulb3.new())
103 end
104
105 if self.aiToolReverserDirectionNode ~= nil or useDefault or self.vehicleAIReverserNode then
106 usedStrategies = usedStrategies .. " +HALFCIRCLE (reverse)"
107 table.insert(self.turnStrategies, AITurnStrategyHalfCircleReverse.new())
108 else
109 usedStrategies = usedStrategies .. " +HALFCIRCLE"
110 table.insert(self.turnStrategies, AITurnStrategyHalfCircle.new())
111 end
112
113 if VehicleDebug.state == VehicleDebug.DEBUG_AI then
114 print("AI is using strategies: "..usedStrategies.." for "..tostring(self.vehicle.configFileName))
115 end
116
117 for _,turnStrategy in ipairs(self.turnStrategies) do
118 turnStrategy:setAIVehicle(self.vehicle, self)
119 end
120
121 self.activeTurnStrategy = nil
122
123 self.turnDataIsStable = false
124 self.turnDataIsStableCounter = 0
125
126 self.fieldEndGabDetected = false
127 self.fieldEndGabLastPos = {}
128 self.gabAllowTurnLeft = true
129 self.gabAllowTurnRight = true
130 self.resetGabDetection = true
131 self.fieldEndGabDetectedByBits = false
132
133 self.lastValidTurnLeftPosition = {0,0,0}
134 self.lastValidTurnLeftValue = 0
135 self.lastValidTurnRightPosition = {0,0,0}
136 self.lastValidTurnRightValue = 0
137 self.lastValidTurnCheckPosition = {0,0,0}
138
139 self.useCorridor = false
140 self.useCorridorStart = nil
141 self.useCorridorTimeOut = 0
142
143 self.rowStartTranslation = nil
144
145 self.lastLookAheadDistance = 5
146
147 self.driveExtraDistanceToFieldBorder = false
148
149 self.toolLineStates = {}
150
151 self.isTurning = false
152 self.isFirstRow = true
153end

update

Description
Definition
update()
Code
157function AIDriveStrategyStraight:update(dt)
158 for _,strategy in ipairs(self.turnStrategies) do
159 strategy:update(dt)
160 end
161end

updateTurnData

Description
Definition
updateTurnData()
Code
960function AIDriveStrategyStraight:updateTurnData()
961 -- calculate basic data, which is needed by turn strategies
962 self.turnData = Utils.getNoNil(self.turnData, {})
963
964 local attachedAIImplements = self.vehicle:getAttachedAIImplements()
965 local vehicleDirectionNode = self.vehicle:getAIDirectionNode()
966 local minTurningRadius = self.vehicle:getAIMinTurningRadius()
967
968 -- determine turning radius
969 self.turnData.radius = self.vehicle.maxTurningRadius * 1.1 -- needs ackermann steering
970 if minTurningRadius ~= nil then
971 self.turnData.radius = math.max(self.turnData.radius, minTurningRadius)
972 end
973
974 local maxToolRadius = 0
975 for _,implement in pairs(attachedAIImplements) do
976 maxToolRadius = math.max(maxToolRadius, AIVehicleUtil.getMaxToolRadius(implement))
977 end
978 self.turnData.radius = math.max(self.turnData.radius, maxToolRadius)
979
980 -- determine tool with smallest AI area and zOffsets for estimation of waypoints/segments
981 local minWidthOfAIArea = math.huge
982 self.turnData.maxZOffset = -math.huge
983 self.turnData.minZOffset = math.huge
984 self.turnData.aiAreaMaxX = -math.huge
985 self.turnData.aiAreaMinX = math.huge
986
987 local lastTypeName = nil
988 local allImplementsOfSameType = true
989 local maxOverlap = -math.huge
990
991 for _,implement in pairs(attachedAIImplements) do
992 if lastTypeName == nil then
993 lastTypeName = implement.object.typeName
994 end
995 local lastVehicleType = g_vehicleTypeManager:getTypeByName(lastTypeName)
996 local vehicleType = g_vehicleTypeManager:getTypeByName(implement.object.typeName)
997
998 allImplementsOfSameType = allImplementsOfSameType and (vehicleType == lastVehicleType
999 or vehicleType.parent == lastVehicleType
1000 or vehicleType == lastVehicleType.parent
1001 or vehicleType.parent == lastVehicleType.parent)
1002
1003 local leftMarker, rightMarker, backMarker = implement.object:getAIMarkers()
1004
1005 local xL,_,zL = localToLocal(leftMarker, vehicleDirectionNode, 0,0,0)
1006 local xR,_,zR = localToLocal(rightMarker, vehicleDirectionNode, 0,0,0)
1007 local xB,_,zB = localToLocal(backMarker, vehicleDirectionNode, 0,0,0)
1008
1009 local lrDistance = math.abs(xL - xR)
1010 if lrDistance < minWidthOfAIArea then
1011 minWidthOfAIArea = lrDistance
1012 self.turnData.minAreaImplement = implement
1013 end
1014
1015 self.turnData.aiAreaMinX = math.min(self.turnData.aiAreaMinX, xL, xR, xB)
1016 self.turnData.aiAreaMaxX = math.max(self.turnData.aiAreaMaxX, xL, xR, xB)
1017
1018 self.turnData.maxZOffset = math.max(self.turnData.maxZOffset, zL, zR)
1019 self.turnData.minZOffset = math.min(self.turnData.minZOffset, zL, zR)
1020
1021 maxOverlap = math.max(maxOverlap, math.min(lrDistance * 0.02, implement.object:getAIAreaOverlap() or AIVehicleUtil.AREA_OVERLAP))
1022 end
1023
1024 self.turnData.allImplementsOfSameType = allImplementsOfSameType
1025
1026 if self.turnData.maxZOffset == self.turnData.minZOffset then
1027 self.turnData.zOffset = 2 * self.turnData.maxZOffset
1028 self.turnData.zOffsetTurn = math.max(1, 2 * self.turnData.maxZOffset)
1029 else
1030 if self.turnData.maxZOffset > 0 and self.turnData.minZOffset < 0 then
1031 self.turnData.zOffset = self.turnData.minZOffset + self.turnData.maxZOffset
1032 self.turnData.zOffsetTurn = math.max(1, self.turnData.minZOffset + self.turnData.maxZOffset)
1033 elseif self.turnData.maxZOffset > 0 and self.turnData.minZOffset > 0 then
1034 self.turnData.zOffset = 2 * self.turnData.maxZOffset
1035 self.turnData.zOffsetTurn = math.max(1, 2 * self.turnData.maxZOffset)
1036 elseif self.turnData.maxZOffset < 0 and self.turnData.minZOffset < 0 then
1037 self.turnData.zOffset = self.turnData.minZOffset + self.turnData.maxZOffset
1038 self.turnData.zOffsetTurn = math.max(1, self.turnData.minZOffset + self.turnData.maxZOffset)
1039 end
1040 end
1041
1042 local minLeftMarker, minRightMarker, _ = self.turnData.minAreaImplement.object:getAIMarkers()
1043 self.turnData.sideOffsetLeft = localToLocal(minLeftMarker, vehicleDirectionNode, 0,0,0)
1044 self.turnData.sideOffsetRight = localToLocal(minRightMarker, vehicleDirectionNode, 0,0,0)
1045
1046 if allImplementsOfSameType then
1047 self.turnData.sideOffsetLeft = self.turnData.aiAreaMaxX
1048 self.turnData.sideOffsetRight = self.turnData.aiAreaMinX
1049 end
1050
1051 -- shrink area size to force overlap (0.26m default)
1052 local overlapPerSide = maxOverlap / 2
1053 self.turnData.sideOffsetLeft = self.turnData.sideOffsetLeft - overlapPerSide
1054 self.turnData.sideOffsetRight = self.turnData.sideOffsetRight + overlapPerSide
1055
1056 -- turn radius should be always higher than the side offset (otherwise some calculation in the strategies won't work)
1057 self.turnData.radius = math.max(self.turnData.radius, self.turnData.sideOffsetLeft, -self.turnData.sideOffsetRight)
1058
1059 -- check if we need to invert the side offset (happens on plows that are rotating)
1060 if self.turnLeft ~= nil then
1061 local canInvertMarkerOnTurn = false
1062 for _, implement in pairs(attachedAIImplements) do
1063 canInvertMarkerOnTurn = canInvertMarkerOnTurn or implement.object:getAIInvertMarkersOnTurn(self.turnLeft)
1064 end
1065
1066 if canInvertMarkerOnTurn then
1067 local offset = math.abs(self.turnData.sideOffsetLeft - self.turnData.sideOffsetRight)/2
1068 self.turnData.sideOffsetLeft = offset
1069 self.turnData.sideOffsetRight = -offset
1070 end
1071 end
1072
1073 self.turnData.useExtraStraightLeft = self.turnData.sideOffsetLeft > self.turnData.radius
1074 self.turnData.useExtraStraightRight = self.turnData.sideOffsetRight < -self.turnData.radius
1075
1076 self.turnData.toolOverhang = {}
1077 self.turnData.toolOverhang['front'] = {}
1078 self.turnData.toolOverhang['back'] = {}
1079
1080
1081 self.turnData.allToolsAtFront = true
1082
1083 -- ToDo: ? improve this fallback estimation of overhang ...
1084 local xt = self.vehicle.size.width * 0.5
1085 local zt = self.vehicle.size.length * 0.75
1086 local alphaX = math.atan(-zt /(xt + self.turnData.radius))
1087 local alphaZ = math.atan((xt + self.turnData.radius) / zt)
1088 local xb = math.cos(alphaX)*xt - math.sin(alphaX)*zt + math.cos(alphaX)*self.turnData.radius
1089 local zb = math.sin(alphaZ)*xt + math.cos(alphaZ)*zt + math.sin(alphaZ)*self.turnData.radius
1090 for _,side in pairs({'front', 'back'}) do
1091 self.turnData.toolOverhang[side].xt = xt
1092 self.turnData.toolOverhang[side].zt = zt
1093 self.turnData.toolOverhang[side].xb = xb
1094 self.turnData.toolOverhang[side].zb = zb
1095 end
1096
1097 -- overhang for tools in hydraulic
1098 for _,implement in pairs(attachedAIImplements) do
1099 local staticObject = implement.object
1100 local isStaticImplement = staticObject:getAIAllowTurnBackward()
1101 if isStaticImplement then
1102 if staticObject.getAttacherVehicle ~= nil then
1103 local attacherVehicle = staticObject:getAttacherVehicle()
1104 if attacherVehicle ~= nil and attacherVehicle.getAIAllowTurnBackward ~= nil then
1105 if not attacherVehicle:getAIAllowTurnBackward() then
1106 isStaticImplement = false
1107 end
1108 end
1109 end
1110 end
1111
1112 if isStaticImplement then
1113 local leftMarker, rightMarker, backMarker = staticObject:getAIMarkers()
1114 local leftSizeMarker, rightSizeMarker, backSizeMarker = staticObject:getAISizeMarkers()
1115
1116 local xL,_,zL = localToLocal(leftSizeMarker or leftMarker, vehicleDirectionNode, 0,0,0)
1117 local xR,_,zR = localToLocal(rightSizeMarker or rightMarker, vehicleDirectionNode, 0,0,0)
1118 local xB,_,zB = localToLocal(backSizeMarker or backMarker, vehicleDirectionNode, 0,0,0)
1119
1120 self.turnData.allToolsAtFront = self.turnData.allToolsAtFront and zB > 0
1121
1122 local xt = math.max(math.abs(xL), math.abs(xR), math.abs(xB))
1123 local zt = math.max(math.abs(zL), math.abs(zR), math.abs(zB))
1124
1125 --local alphaX = math.atan(-zt /(xt + self.turnData.radius))
1126 --local alphaZ = math.atan((xt + self.turnData.radius) / zt)
1127 --
1128 --local xb = math.cos(alphaX)*xt - math.sin(alphaX)*zt + math.cos(alphaX)*self.turnData.radius
1129 --local zb = math.sin(alphaZ)*xt + math.cos(alphaZ)*zt + math.sin(alphaZ)*self.turnData.radius
1130 --
1131 ----print(" --------------------------------------------------- ")
1132 ----print( string.format(" OLD :: alphaX=%.2f alphaZ=%.2f ", math.deg(alphaX), math.deg(alphaZ)) )
1133 ----print( string.format(" -> xb /zb = %.2f / %.2f ", xb, zb) )
1134 ----
1135 ----
1136 ----local alphaX = math.atan( -(self.turnData.radius + xt) / zt )
1137 ----local alphaZ = math.atan( xt / (self.turnData.radius + zt) )
1138 ----
1139 ----local xb = math.cos(alphaX)*xt - math.sin(alphaX)*zt + math.cos(alphaX)*self.turnData.radius
1140 ----local zb = math.sin(alphaZ)*xt + math.cos(alphaZ)*zt + math.sin(alphaZ)*self.turnData.radius
1141 ----
1142 ----print( string.format(" NEW :: alphaX=%.2f alphaZ=%.2f ", math.deg(alphaX), math.deg(alphaZ)) )
1143 ----print( string.format(" -> xb /zb = %.2f / %.2f ", xb, zb) )
1144 ----print( string.format(" -> xt /zt = %.2f / %.2f ", xt, zt) )
1145 ----
1146 ----print( string.format(" -> c = %.2f / r = %.2f / c+r = %.2f", math.sqrt(xt*xt + zt*zt), self.turnData.radius, math.sqrt(xt*xt + zt*zt)+self.turnData.radius) )
1147
1148 -- dismiss all the math, just take the worst case
1149 local xb = math.sqrt(xt*xt + zt*zt) + self.turnData.radius
1150 local zb = math.sqrt(xt*xt + zt*zt) + self.turnData.radius
1151
1152
1153 local side = 'back'
1154 if zB > 0 then
1155 side = 'front'
1156 end
1157
1158 self.turnData.toolOverhang[side].xb = math.max(xb, self.turnData.toolOverhang[side].xb)
1159 self.turnData.toolOverhang[side].zb = math.max(zb, self.turnData.toolOverhang[side].zb)
1160 self.turnData.toolOverhang[side].xt = math.max(xt, self.turnData.toolOverhang[side].xt)
1161 self.turnData.toolOverhang[side].zt = math.max(zt, self.turnData.toolOverhang[side].zt)
1162 end
1163 end
1164
1165 -- overhang for trailer implements
1166 local rotTime = 1/self.vehicle.wheelSteeringDuration * math.atan(1/self.turnData.radius) / math.atan(1/self.vehicle.maxTurningRadius)
1167 local angle
1168 if rotTime >= 0 then
1169 angle = (rotTime / self.vehicle.maxRotTime) * self.vehicle.maxRotation
1170 else
1171 angle = (rotTime / self.vehicle.minRotTime) * self.vehicle.maxRotation
1172 end
1173
1174 for _,implement in pairs(attachedAIImplements) do
1175 local dynamicObject = implement.object
1176 local isDynamicImplement = not dynamicObject:getAIAllowTurnBackward()
1177 if not isDynamicImplement then
1178 if dynamicObject.getAttacherVehicle ~= nil then
1179 local attacherVehicle = dynamicObject:getAttacherVehicle()
1180 if attacherVehicle ~= nil and attacherVehicle.getAIAllowTurnBackward ~= nil then
1181 if not attacherVehicle:getAIAllowTurnBackward() then
1182 isDynamicImplement = true
1183 dynamicObject = attacherVehicle
1184 end
1185 end
1186 end
1187 end
1188
1189 if isDynamicImplement then
1190 local leftMarker, rightMarker, backMarker = implement.object:getAIMarkers()
1191 local leftSizeMarker, rightSizeMarker, backSizeMarker = implement.object:getAISizeMarkers()
1192
1193 local lX,_,lZ = localToLocal(leftSizeMarker or leftMarker, dynamicObject.components[1].node, 0,0,0)
1194 local rX,_,rZ = localToLocal(rightSizeMarker or rightMarker, dynamicObject.components[1].node, 0,0,0)
1195 local bX,_,bZ = localToLocal(backSizeMarker or backMarker, dynamicObject.components[1].node, 0,0,0)
1196
1197 local nX = math.max( math.abs(lX), math.abs(rX), math.abs(bX) )
1198 local nZ = math.min( -math.abs(lZ), -math.abs(rZ), -math.abs(bZ) )
1199
1200 if dynamicObject.getActiveInputAttacherJoint then
1201 local inputAttacherJoint = dynamicObject:getActiveInputAttacherJoint()
1202
1203 local xAtt,_,zAtt = localToLocal(dynamicObject.components[1].node, inputAttacherJoint.node, nX,0,nZ)
1204 xAtt, zAtt = zAtt, -xAtt
1205
1206 local xRot = xAtt*math.cos(-angle) - zAtt*math.sin(-angle)
1207 local zRot = xAtt*math.sin(-angle) + zAtt*math.cos(-angle)
1208
1209 local xFin,_,_ = localToLocal(dynamicObject.components[1].node, vehicleDirectionNode, xRot,0,zRot)
1210
1211 xFin = xFin + self.turnData.radius
1212
1213 self.turnData.toolOverhang.back.xb = math.max(self.turnData.toolOverhang.back.xb, xFin)
1214
1215 local xL,_,_ = localToLocal(leftSizeMarker or leftMarker, vehicleDirectionNode, 0,0,0)
1216 local xR,_,_ = localToLocal(rightSizeMarker or rightMarker, vehicleDirectionNode, 0,0,0)
1217 local _,_,zB = localToLocal(backSizeMarker or backMarker, vehicleDirectionNode, 0,0,0)
1218 self.turnData.toolOverhang.back.xt = math.max(self.turnData.toolOverhang.back.xt, math.abs(xL), math.abs(xR))
1219 self.turnData.toolOverhang.back.zt = math.max(self.turnData.toolOverhang.back.zt, -zB)
1220
1221 local _, rotationJoint, wheels = dynamicObject:getAITurnRadiusLimitation()
1222
1223
1224 local angleSteer = 0
1225 if rotationJoint ~= nil then
1226 for _, wheel in pairs(wheels) do
1227 if wheel.steeringAxleScale ~= 0 and wheel.steeringAxleRotMax ~= 0 then
1228 angleSteer = math.max(angleSteer, math.abs(wheel.steeringAxleRotMax))
1229 end
1230 end
1231 end
1232
1233 if angleSteer ~= 0 and rotationJoint ~= nil then
1234 local wheelIndexCount = #wheels
1235 if rotationJoint ~= nil and wheelIndexCount > 0 then
1236 local cx,cz = 0,0
1237 for _, wheel in pairs(wheels) do
1238 local x,_,z = localToLocal(wheel.repr, dynamicObject.components[1].node, 0,0,0)
1239 cx = cx + x
1240 cz = cz + z
1241 end
1242
1243 cx = cx / wheelIndexCount
1244 cz = cz / wheelIndexCount
1245
1246 local dx = nX - cx
1247 local dz = nZ - cz
1248
1249 local delta = math.sqrt(dx*dx + dz*dz)
1250 local xFin = delta + self.turnData.radius
1251
1252 self.turnData.toolOverhang.back.xb = math.max(self.turnData.toolOverhang.back.xb, xFin)
1253 end
1254 end
1255 end
1256 end
1257 end
1258
1259 for _,implement in pairs(attachedAIImplements) do
1260 local leftMarker, _, _ = implement.object:getAIMarkers()
1261 local _,_,z = localToLocal(leftMarker, vehicleDirectionNode, 0,0,0)
1262 implement.distToVehicle = z
1263 end
1264
1265 local sortImplementsByDistance = function(arg1, arg2)
1266 return arg1.distToVehicle > arg2.distToVehicle
1267 end
1268
1269 table.sort(attachedAIImplements, sortImplementsByDistance)
1270
1271 -- check if turn data is stable
1272 if self.lastTurnData == nil then
1273 self.lastTurnData = {}
1274 self.lastTurnData.radius = self.turnData.radius
1275 self.lastTurnData.maxZOffset = self.turnData.maxZOffset
1276 self.lastTurnData.minZOffset = self.turnData.minZOffset
1277 self.lastTurnData.aiAreaMaxX = self.turnData.aiAreaMaxX
1278 self.lastTurnData.aiAreaMinX = self.turnData.aiAreaMinX
1279 self.lastTurnData.sideOffsetLeft = self.turnData.sideOffsetLeft
1280 self.lastTurnData.sideOffsetRight = self.turnData.sideOffsetRight
1281 else
1282 if self.vehicle:getLastSpeed() > 2 and
1283 math.abs(self.lastTurnData.radius - self.turnData.radius) < 0.03 and
1284 math.abs(self.lastTurnData.maxZOffset - self.turnData.maxZOffset) < 0.03 and
1285 math.abs(self.lastTurnData.minZOffset - self.turnData.minZOffset) < 0.03 and
1286 math.abs(self.lastTurnData.aiAreaMaxX - self.turnData.aiAreaMaxX) < 0.03 and
1287 math.abs(self.lastTurnData.aiAreaMinX - self.turnData.aiAreaMinX) < 0.03 and
1288 math.abs(self.lastTurnData.sideOffsetLeft - self.turnData.sideOffsetLeft) < 0.03 and
1289 math.abs(self.lastTurnData.sideOffsetRight - self.turnData.sideOffsetRight) < 0.03
1290 then
1291 self.turnDataIsStableCounter = self.turnDataIsStableCounter + 1
1292 if self.turnDataIsStableCounter > 120 then
1293 self.turnDataIsStable = true
1294 end
1295 else
1296 self.lastTurnData.radius = self.turnData.radius
1297 self.lastTurnData.maxZOffset = self.turnData.maxZOffset
1298 self.lastTurnData.minZOffset = self.turnData.minZOffset
1299 self.lastTurnData.aiAreaMaxX = self.turnData.aiAreaMaxX
1300 self.lastTurnData.aiAreaMinX = self.turnData.aiAreaMinX
1301 self.lastTurnData.sideOffsetLeft = self.turnData.sideOffsetLeft
1302 self.lastTurnData.sideOffsetRight = self.turnData.sideOffsetRight
1303 self.turnDataIsStableCounter = 0
1304 end
1305 end
1306end