948 | function AITurnStrategy.drawTurnSegments(segments) |
949 | for i,segment in pairs(segments) do |
950 | if segment.isCurve == true then |
951 | local oX,oY,oZ = localToWorld(segment.o, 0,2,0) |
952 | local xX,xY,xZ = localToWorld(segment.o, 2,2,0) |
953 | local yX,yY,yZ = localToWorld(segment.o, 0,4,0) |
954 | local zX,zY,zZ = localToWorld(segment.o, 0,2,2) |
955 | |
956 | drawDebugLine(oX,oY,oZ, 1,0,0, xX,xY,xZ, 1,0,0) |
957 | drawDebugLine(oX,oY,oZ, 0,1,0, yX,yY,yZ, 0,1,0) |
958 | drawDebugLine(oX,oY,oZ, 0,0,1, zX,zY,zZ, 0,0,1) |
959 | |
960 | Utils.renderTextAtWorldPosition(yX,yY,yZ, tostring(i), 0.02, 0) |
961 | |
962 | local ts = 20 |
963 | for i=0,ts-1 do |
964 | local x1 = segment.radius * math.cos(segment.startAngle + i*(segment.endAngle-segment.startAngle)/ts) |
965 | local z1 = segment.radius * math.sin(segment.startAngle + i*(segment.endAngle-segment.startAngle)/ts) |
966 | local x2 = segment.radius * math.cos(segment.startAngle + (i+1)*(segment.endAngle-segment.startAngle)/ts) |
967 | local z2 = segment.radius * math.sin(segment.startAngle + (i+1)*(segment.endAngle-segment.startAngle)/ts) |
968 | local w1X,w1Y,w1Z = localToWorld(segment.o, x1,0,z1) |
969 | local w2X,w2Y,w2Z = localToWorld(segment.o, x2,0,z2) |
970 | local w1Y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, w1X,w1Y,w1Z) + 1 |
971 | local w2Y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, w2X,w2Y,w2Z) + 1 |
972 | drawDebugLine(w1X,w1Y,w1Z, (ts-i)/ts,i/ts,0, w2X,w2Y,w2Z, (ts-i-1)/ts,(i+1)/ts,0) |
973 | end |
974 | else |
975 | local sY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, unpack(segment.startPoint)) + 1 |
976 | local eY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, unpack(segment.endPoint)) + 1 |
977 | drawDebugLine(segment.startPoint[1],sY,segment.startPoint[3], 1,0,0, segment.endPoint[1],eY,segment.endPoint[3], 0,1,0) |
978 | |
979 | drawDebugLine(segment.startPoint[1],sY,segment.startPoint[3], 1,1,1, segment.startPoint[1],sY+2,segment.startPoint[3], 1,1,1) |
980 | drawDebugLine(segment.endPoint[1],sY,segment.endPoint[3], 1,1,1, segment.endPoint[1],sY+2,segment.endPoint[3], 1,1,1) |
981 | |
982 | Utils.renderTextAtWorldPosition((segment.startPoint[1]+segment.endPoint[1])/2, (sY+eY)/2, (segment.startPoint[3]+segment.endPoint[3])/2, tostring(i), 0.02, 0) |
983 | end |
984 | end |
985 | end |
476 | function AITurnStrategy:getDistanceToCollision(dt, vX,vY,vZ, turnData, lookAheadDistance) |
477 | local allowLeft = self.usesExtraStraight == turnData.useExtraStraightLeft |
478 | local allowRight = self.usesExtraStraight == turnData.useExtraStraightRight |
479 | |
480 | local distanceToTurn = lookAheadDistance |
481 | |
482 | -- Disable this turn strategy immediately if we are supposed to turn in a direction that is not allwoed |
483 | -- or if the only allowed side does not have any work to be done |
484 | if not allowLeft and not allowRight then |
485 | distanceToTurn = -1 |
486 | elseif self.turnLeft ~= nil then |
487 | if (self.turnLeft and not allowRight) or (not self.turnLeft and not allowLeft) then |
488 | distanceToTurn = -1 |
489 | end |
490 | else |
491 | local allowLeftWithCol = allowLeft and self.collisionEndPosLeft == nil |
492 | local allowRightWithCol = allowRight and self.collisionEndPosRight == nil |
493 | |
494 | -- Turn if not allowed or has collision on one side and the other side has no work to be done |
495 | if not allowLeftWithCol or not allowRightWithCol then |
496 | local leftAreaPercentage, rightAreaPercentage = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle, turnData) |
497 | |
498 | if allowLeftWithCol and leftAreaPercentage <= 3*AIVehicleUtil.VALID_AREA_THRESHOLD then |
499 | distanceToTurn = -1 |
500 | end |
501 | if allowRightWithCol and rightAreaPercentage <= 3*AIVehicleUtil.VALID_AREA_THRESHOLD then |
502 | distanceToTurn = -1 |
503 | end |
504 | end |
505 | end |
506 | |
507 | if self.collisionDetected then |
508 | if self.collisionDetectedPosX ~= nil then |
509 | local dist = MathUtil.vector3Length(vX-self.collisionDetectedPosX, vY-self.collisionDetectedPosY, vZ-self.collisionDetectedPosZ) |
510 | distanceToTurn = math.min(distanceToTurn, lookAheadDistance - dist) |
511 | else |
512 | distanceToTurn = -1 |
513 | end |
514 | end |
515 | |
516 | self.distanceToCollision = distanceToTurn |
517 | |
518 | -- increase the size of the collision check boxes more to the back |
519 | -- 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 |
520 | local boxLookBackDistance = 0 |
521 | if self.parent ~= nil then |
522 | if self.parent.rowStartTranslation ~= nil then |
523 | local x, _, z = getWorldTranslation(self.vehicle:getAIVehicleDirectionNode()) |
524 | boxLookBackDistance = math.min(MathUtil.vector2Length(x-self.parent.rowStartTranslation[1], z-self.parent.rowStartTranslation[3]) * 0.66, 5) |
525 | end |
526 | end |
527 | |
528 | -- |
529 | for i=#self.maxTurningSizeBoxes, 1, -1 do |
530 | self.maxTurningSizeBoxes[i] = nil |
531 | end |
532 | |
533 | local collisionHitLeft = false |
534 | local collisionHitRight = false |
535 | |
536 | if (self.turnLeft == nil or self.turnLeft == false) and allowLeft then |
537 | local turnLeft = true |
538 | self:updateTurningSizeBox(self.leftBox, turnLeft, turnData, math.max(0,distanceToTurn)) |
539 | local box = self.leftBox |
540 | box.center[3] = box.center[3]-boxLookBackDistance/2 |
541 | box.size[3] = box.size[3]+boxLookBackDistance |
542 | |
543 | if not self:validateCollisionBox(box) then |
544 | self.vehicle.stopAIVehicle(AIVehicle.STOP_REASON_UNKOWN) |
545 | return distanceToTurn |
546 | end |
547 | |
548 | collisionHitLeft = self:getIsBoxColliding(box) |
549 | |
550 | table.insert(self.maxTurningSizeBoxes, box) |
551 | |
552 | if collisionHitLeft and self.collisionEndPosLeft == nil then |
553 | self.collisionEndPosLeft = { localToWorld(self.vehicleDirectionNode, 0,0,box.size[3]) } |
554 | end |
555 | end |
556 | |
557 | if (self.turnLeft == nil or self.turnLeft == true) and allowRight then |
558 | local turnLeft = false |
559 | self:updateTurningSizeBox(self.rightBox, turnLeft, turnData, math.max(0,distanceToTurn)) |
560 | local box = self.rightBox |
561 | box.center[3] = box.center[3]-boxLookBackDistance/2 |
562 | box.size[3] = box.size[3]+boxLookBackDistance |
563 | |
564 | if not self:validateCollisionBox(box) then |
565 | self.vehicle.stopAIVehicle(AIVehicle.STOP_REASON_UNKOWN) |
566 | return distanceToTurn |
567 | end |
568 | |
569 | collisionHitRight = self:getIsBoxColliding(box) |
570 | |
571 | table.insert(self.maxTurningSizeBoxes, box) |
572 | |
573 | if collisionHitRight and self.collisionEndPosRight == nil then |
574 | self.collisionEndPosRight = { localToWorld(self.vehicleDirectionNode, 0,0,box.size[3]) } |
575 | end |
576 | end |
577 | |
578 | self:evaluateCollisionHits(vX,vY,vZ, collisionHitLeft, collisionHitRight, turnData) |
579 | |
580 | return distanceToTurn |
581 | end |
165 | function AITurnStrategy:getDriveData(dt, vX,vY,vZ, turnData) |
166 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
167 | self.vehicle:addAIDebugText(string.format("strategy: %s", self.strategyName)) |
168 | end |
169 | |
170 | local tX, tY, tZ |
171 | local maxSpeed = self.vehicle:getSpeedLimit() |
172 | maxSpeed = math.min(14, maxSpeed) |
173 | local distanceToStop |
174 | |
175 | local segment = self.turnSegments[self.activeTurnSegmentIndex] |
176 | local segmentIsFinished = false |
177 | |
178 | local moveForwards = segment.moveForward |
179 | |
180 | if segment.isCurve then |
181 | local angleDirSign = MathUtil.sign(segment.endAngle - segment.startAngle) |
182 | |
183 | local curAngle |
184 | if self.reverserDirectionNode ~= nil and not moveForwards then |
185 | curAngle = AITurnStrategy.getAngleInSegment(self.reverserDirectionNode, segment) |
186 | else |
187 | curAngle = AITurnStrategy.getAngleInSegment(self.vehicleAISteeringNode, segment) |
188 | end |
189 | |
190 | local nextAngleDistance = math.max(3, 0.33 * self.vehicle.maxTurningRadius) |
191 | |
192 | local nextAngle = curAngle + angleDirSign * nextAngleDistance / segment.radius |
193 | if nextAngle > math.pi then |
194 | nextAngle = nextAngle - 2*math.pi |
195 | elseif nextAngle < -math.pi then |
196 | nextAngle = nextAngle + 2*math.pi |
197 | end |
198 | |
199 | local endAngle = segment.endAngle |
200 | if endAngle > math.pi then |
201 | endAngle = endAngle - 2*math.pi |
202 | elseif endAngle < -math.pi then |
203 | endAngle = endAngle + 2*math.pi |
204 | end |
205 | |
206 | angleDirSign = MathUtil.sign(segment.endAngle - segment.startAngle) |
207 | |
208 | local curAngleDiff = angleDirSign * (curAngle - endAngle) -- > 0 if after endAngle |
209 | if curAngleDiff > math.rad(10) then |
210 | curAngleDiff = curAngleDiff - 2*math.pi |
211 | elseif curAngleDiff < -2*math.pi + math.rad(10) then |
212 | curAngleDiff = curAngleDiff + 2*math.pi |
213 | end |
214 | |
215 | local nextAngleDiff = angleDirSign * (nextAngle - endAngle) |
216 | if nextAngleDiff > math.rad(10) then |
217 | nextAngleDiff = nextAngleDiff - 2*math.pi |
218 | elseif nextAngleDiff < -2*math.pi + math.rad(10) then |
219 | nextAngleDiff = nextAngleDiff + 2*math.pi |
220 | end |
221 | |
222 | local pX = math.cos(nextAngle) * segment.radius |
223 | local pZ = math.sin(nextAngle) * segment.radius |
224 | tX,tY,tZ = localToWorld(segment.o, pX,0,pZ) |
225 | |
226 | -- condition for segment finish |
227 | distanceToStop = -curAngleDiff * segment.radius |
228 | |
229 | if distanceToStop < 0.01 or (segment.usePredictionToSkipToNextSegment ~= false and nextAngleDiff > 0) then |
230 | segmentIsFinished = true |
231 | end |
232 | |
233 | if segment.checkForSkipToNextSegment then |
234 | local nextSegment = self.turnSegments[self.activeTurnSegmentIndex+1] |
235 | |
236 | local dirX = nextSegment.endPoint[1] - nextSegment.startPoint[1] |
237 | local dirZ = nextSegment.endPoint[3] - nextSegment.startPoint[3] |
238 | local dirLength = MathUtil.vector2Length(dirX, dirZ) |
239 | local dx,_,_ |
240 | if self.reverserDirectionNode ~= nil and not moveForwards then |
241 | dx,_,_ = worldDirectionToLocal(self.reverserDirectionNode, dirX/dirLength,0,dirZ/dirLength) |
242 | else |
243 | dx,_,_ = worldDirectionToLocal(self.vehicleAISteeringNode, dirX/dirLength,0,dirZ/dirLength) |
244 | end |
245 | |
246 | local l = MathUtil.vector2Length(dirX, dirZ) |
247 | dirX, dirZ = dirX / l, dirZ / l |
248 | pX, pZ = MathUtil.projectOnLine(vX, vZ, nextSegment.startPoint[1], nextSegment.startPoint[3], dirX, dirZ) |
249 | local dist = MathUtil.vector2Length(vX-pX, vZ-pZ) |
250 | |
251 | local distToStart = MathUtil.vector2Length(vX-nextSegment.startPoint[1], vZ-nextSegment.startPoint[3]) |
252 | |
253 | if dist < 1.5 and math.abs(dx) < 0.15 and distToStart < self.vehicle.sizeLength/2 then |
254 | segmentIsFinished = true |
255 | end |
256 | end |
257 | |
258 | -- |
259 | if not moveForwards and self.reverserDirectionNode ~= nil then |
260 | local x,_,z = worldToLocal(self.reverserDirectionNode, tX,vY,tZ) |
261 | local alpha = Utils.getYRotationBetweenNodes(self.vehicleAISteeringNode, self.reverserDirectionNode) |
262 | local ltX = math.cos(alpha)*x - math.sin(alpha)*z |
263 | local ltZ = math.sin(alpha)*x + math.cos(alpha)*z |
264 | ltX = -ltX |
265 | tX,_,tZ = localToWorld(self.vehicleAISteeringNode, ltX,0,ltZ) |
266 | end |
267 | |
268 | -- just visual debuging |
269 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
270 | drawDebugLine(vX,vY+2,vZ, 1,1,0, tX,tY+2,tZ, 1,1,0) |
271 | end |
272 | else |
273 | local toolX,_,toolZ |
274 | if self.reverserDirectionNode ~= nil then |
275 | toolX,_,toolZ = getWorldTranslation(self.reverserDirectionNode) |
276 | end |
277 | |
278 | local dirX = segment.endPoint[1] - segment.startPoint[1] |
279 | local dirZ = segment.endPoint[3] - segment.startPoint[3] |
280 | |
281 | local l = MathUtil.vector2Length(dirX, dirZ) |
282 | dirX, dirZ = dirX / l, dirZ / l |
283 | local pX, pZ |
284 | if self.reverserDirectionNode ~= nil and not moveForwards then |
285 | pX, pZ = MathUtil.projectOnLine(toolX, toolZ, segment.startPoint[1], segment.startPoint[3], dirX, dirZ) |
286 | else |
287 | if self.vehicleAIReverserNode ~= nil and not moveForwards then |
288 | toolX,_,toolZ = getWorldTranslation(self.vehicleAIReverserNode) |
289 | pX, pZ = MathUtil.projectOnLine(toolX, toolZ, segment.startPoint[1], segment.startPoint[3], dirX, dirZ) |
290 | else |
291 | pX, pZ = MathUtil.projectOnLine(vX, vZ, segment.startPoint[1], segment.startPoint[3], dirX, dirZ) |
292 | end |
293 | end |
294 | |
295 | local factor = 1.0 |
296 | tX = pX + (dirX * factor * self.vehicle.maxTurningRadius) |
297 | tZ = pZ + (dirZ * factor * self.vehicle.maxTurningRadius) |
298 | |
299 | if self.reverserDirectionNode ~= nil and not moveForwards then |
300 | local x,_,z = worldToLocal(self.reverserDirectionNode, tX,vY,tZ) |
301 | local alpha = Utils.getYRotationBetweenNodes(self.vehicleAISteeringNode, self.reverserDirectionNode) |
302 | |
303 | local articulatedAxisSpec = self.vehicle.spec_articulatedAxis |
304 | if articulatedAxisSpec ~= nil and articulatedAxisSpec.componentJoint ~= nil then |
305 | local node1 = self.vehicle.components[articulatedAxisSpec.componentJoint.componentIndices[1]].node |
306 | local node2 = self.vehicle.components[articulatedAxisSpec.componentJoint.componentIndices[2]].node |
307 | if articulatedAxisSpec.anchorActor == 1 then |
308 | node1, node2 = node2, node1 |
309 | end |
310 | |
311 | local beta = Utils.getYRotationBetweenNodes(node1, node2) |
312 | alpha = alpha - beta |
313 | end |
314 | |
315 | local ltX = math.cos(alpha)*x - math.sin(alpha)*z |
316 | local ltZ = math.sin(alpha)*x + math.cos(alpha)*z |
317 | ltX = -ltX |
318 | tX,_,tZ = localToWorld(self.vehicleAISteeringNode, ltX,0,ltZ) |
319 | end |
320 | |
321 | distanceToStop = MathUtil.vector3Length(segment.endPoint[1]-vX, segment.endPoint[2]-vY, segment.endPoint[3]-vZ) |
322 | |
323 | |
324 | local _,_,lz = worldToLocal(self.vehicleAISteeringNode, segment.endPoint[1],segment.endPoint[2],segment.endPoint[3]) |
325 | if (segment.moveForward and lz < 0) or (not segment.moveForward and lz > 0) then |
326 | segmentIsFinished = true |
327 | end |
328 | |
329 | -- check during pre final straight, only used by reverse strategies |
330 | if segment.checkAlignmentToSkipSegment then |
331 | local d1x,_,d1z = localDirectionToWorld(self.vehicleAISteeringNode, 0,0,1) |
332 | local l1 = MathUtil.vector2Length(d1x, d1z) |
333 | d1x, d1z = d1x/l1, d1z/l1 |
334 | local a1 = math.acos( d1x * dirX + d1z * dirZ ) |
335 | local dist = MathUtil.vector2Length(vX-pX, vZ-pZ) |
336 | local canSkip = math.deg(a1) < 8 and dist < 0.6 |
337 | |
338 | if self.vehicle.spec_articulatedAxis ~= nil and self.vehicle.spec_articulatedAxis.componentJoint ~= nil then |
339 | for i=1,2 do |
340 | local node = self.vehicle.components[self.vehicle.spec_articulatedAxis.componentJoint.componentIndices[i]].node |
341 | |
342 | d1x,_,d1z = localDirectionToWorld(node, 0,0,1) |
343 | l1 = MathUtil.vector2Length(d1x, d1z) |
344 | d1x, d1z = d1x/l1, d1z/l1 |
345 | local a = math.acos( d1x * dirX + d1z * dirZ ) |
346 | canSkip = canSkip and math.deg(a) < 8 |
347 | end |
348 | end |
349 | |
350 | if self.reverserDirectionNode ~= nil then |
351 | local d2x,_,d2z = localDirectionToWorld(self.reverserDirectionNode, 0,0,1) |
352 | local l2 = MathUtil.vector2Length(d2x, d2z) |
353 | d2x, d2z = d2x/l2, d2z/l2 |
354 | local a2 = math.acos( d2x * dirX + d2z * dirZ ) |
355 | pX, pZ = MathUtil.projectOnLine(toolX,toolZ, segment.startPoint[1],segment.startPoint[3], dirX,dirZ) |
356 | dist = MathUtil.vector2Length(toolX-pX, toolZ-pZ) |
357 | canSkip = canSkip and math.deg(a2) < 6 and dist < 0.6 |
358 | end |
359 | |
360 | local nextSegment = self.turnSegments[self.activeTurnSegmentIndex+1] |
361 | local _,_,sz = worldToLocal(self.vehicleDirectionNode, nextSegment.startPoint[1],nextSegment.startPoint[2],nextSegment.startPoint[3]) |
362 | local _,_,ez = worldToLocal(self.vehicleDirectionNode, nextSegment.endPoint[1],nextSegment.endPoint[2],nextSegment.endPoint[3]) |
363 | canSkip = canSkip and (sz < 0 or ez < 0) |
364 | |
365 | if canSkip then |
366 | segmentIsFinished = true |
367 | end |
368 | end |
369 | |
370 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
371 | local sY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, tX,vY,tZ) |
372 | drawDebugLine(vX,vY+2,vZ, 1,1,0, tX,sY+2,tZ, 1,1,0) |
373 | end |
374 | end |
375 | |
376 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
377 | self.vehicle:addAIDebugText(string.format("active segment: %d", self.activeTurnSegmentIndex)) |
378 | end |
379 | |
380 | --# check if a tool can already work |
381 | if segment.checkForValidArea then |
382 | local lookAheadDist = 0 |
383 | local lookAheadSize = 1 |
384 | if not moveForwards then |
385 | lookAheadSize = -1 |
386 | end |
387 | if AIVehicleUtil.checkImplementListForValidGround(self.vehicle, lookAheadDist, lookAheadSize) then |
388 | segmentIsFinished = true |
389 | self.activeTurnSegmentIndex = #self.turnSegments |
390 | end |
391 | end |
392 | |
393 | --# |
394 | if segment.findEndOfField then |
395 | local lookAheadDist = 0 |
396 | local lookAheadSize = 1 |
397 | if not moveForwards then |
398 | lookAheadSize = -1 |
399 | end |
400 | if not AIVehicleUtil.checkImplementListForValidGround(self.vehicle, lookAheadDist, lookAheadSize) then |
401 | segmentIsFinished = true |
402 | end |
403 | end |
404 | |
405 | -- activate next segment or stop turn |
406 | if segmentIsFinished then |
407 | self.activeTurnSegmentIndex = self.activeTurnSegmentIndex + 1 |
408 | |
409 | if self.turnSegments[self.activeTurnSegmentIndex] == nil then |
410 | self.isTurning = false |
411 | return nil |
412 | end |
413 | end |
414 | |
415 | -- calculate turn progress percentage |
416 | local totalSegmentLength = 0 |
417 | local usedSegmentDistance = 0 |
418 | for i, turnSegment in ipairs(self.turnSegments) do |
419 | -- exclude the last straight part(s) since we don't know how far we need to go |
420 | if turnSegment.checkAlignmentToSkipSegment then |
421 | break |
422 | end |
423 | |
424 | local segmentLength |
425 | if turnSegment.isCurve then |
426 | segmentLength = math.abs(turnSegment.endAngle - turnSegment.startAngle) * turnSegment.radius |
427 | else |
428 | segmentLength = math.abs(MathUtil.vector3Length(turnSegment.endPoint[1]-turnSegment.startPoint[1], |
429 | turnSegment.endPoint[2]-turnSegment.startPoint[2], |
430 | turnSegment.endPoint[3]-turnSegment.startPoint[3])) |
431 | end |
432 | segmentLength = math.abs(segmentLength) |
433 | |
434 | totalSegmentLength = totalSegmentLength + segmentLength |
435 | |
436 | if i < self.activeTurnSegmentIndex then |
437 | usedSegmentDistance = usedSegmentDistance + segmentLength |
438 | elseif i == self.activeTurnSegmentIndex then |
439 | usedSegmentDistance = usedSegmentDistance + (segmentLength-distanceToStop) |
440 | end |
441 | end |
442 | |
443 | local turnProgress = usedSegmentDistance / totalSegmentLength |
444 | self.vehicle:aiTurnProgress(turnProgress, self.turnLeft) |
445 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
446 | self.vehicle:addAIDebugText(string.format("turn progress: %.1f%%", turnProgress*100)) |
447 | end |
448 | |
449 | if not segment.slowDown then |
450 | distanceToStop = math.huge |
451 | end |
452 | |
453 | return tX, tZ, moveForwards, maxSpeed, distanceToStop |
454 | end |
585 | function AITurnStrategy:getIsBoxColliding(box) |
586 | box.x, box.y, box.z = localToWorld(self.vehicleDirectionNode, box.center[1], box.center[2], box.center[3]) |
587 | box.zx, box.zy, box.zz = localDirectionToWorld(self.vehicleDirectionNode, 0,0,1) |
588 | box.xx, box.xy, box.xz = localDirectionToWorld(self.vehicleDirectionNode, 1,0,0) |
589 | box.ry = math.atan2(box.zx, box.zz) |
590 | box.color = AITurnStrategy.COLLISION_BOX_COLOR_OK |
591 | |
592 | self.collisionHit = false |
593 | overlapBox(box.x,box.y,box.z, 0,box.ry,0, box.size[1],box.size[2],box.size[3], "collisionTestCallback", self, AIVehicleUtil.COLLISION_MASK, true, true, true) |
594 | if self.collisionHit then |
595 | box.color = AITurnStrategy.COLLISION_BOX_COLOR_HIT |
596 | return true |
597 | end |
598 | |
599 | local x1, _, z1 = localToWorld(self.vehicleDirectionNode, box.center[1]+box.size[1], 0, box.center[3]+box.size[3]) |
600 | local x2, _, z2 = localToWorld(self.vehicleDirectionNode, box.center[1]+box.size[1], 0, box.center[3]-box.size[3]) |
601 | |
602 | local t1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1, 0, z1) |
603 | local t2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2, 0, z2) |
604 | |
605 | -- check if at the box start or end position is water |
606 | if t1 < g_currentMission.waterY or t2 < g_currentMission.waterY then |
607 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
608 | self.vehicle:addAIDebugText(string.format(" hit water: b%.1f f%.1f w%.1f", t1, t2, g_currentMission.waterY)) |
609 | end |
610 | |
611 | box.color = AITurnStrategy.COLLISION_BOX_COLOR_HIT |
612 | return true |
613 | end |
614 | |
615 | local testLength = 3 |
616 | |
617 | -- left and right side of the box |
618 | 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) |
619 | 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) |
620 | |
621 | -- center of vehicle |
622 | local angle3 = self:getCollisionBoxSlope(self.vehicleDirectionNode, 0, 0, box.center[3]+box.size[3], 0, 0, box.center[3]+box.size[3]-testLength) |
623 | |
624 | -- side angle of box |
625 | 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]) |
626 | 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]) |
627 | |
628 | local angleBetween = math.max(angle1, angle2, angle3, angle4, angle5) |
629 | |
630 | if angleBetween > AITurnStrategy.SLOPE_DETECTION_THRESHOLD then |
631 | box.color = AITurnStrategy.COLLISION_BOX_COLOR_HIT |
632 | return true |
633 | end |
634 | |
635 | -- check for density height heaps higher than 2m |
636 | x1, _, z1 = localToWorld(self.vehicleDirectionNode, box.center[1]+box.size[1], 0, box.center[3]+box.size[3]) |
637 | x2, _, z2 = localToWorld(self.vehicleDirectionNode, box.center[1]-box.size[1], 0, box.center[3]+box.size[3]) |
638 | local length = MathUtil.vector2Length(x1-x2, z1-z2) |
639 | local steps = math.floor(length/3) |
640 | for i=0,steps do |
641 | local alpha = math.min(1/(steps+1)*(i+math.random()), 1) |
642 | local x, z = MathUtil.lerp(x1, x2, alpha), MathUtil.lerp(z1, z2, alpha) |
643 | local _, densityHeight = DensityMapHeightUtil.getHeightAtWorldPos(x, 0, z) |
644 | |
645 | if densityHeight >= AITurnStrategy.DENSITY_HEIGHT_THRESHOLD then |
646 | box.color = AITurnStrategy.COLLISION_BOX_COLOR_HIT |
647 | return true |
648 | end |
649 | end |
650 | |
651 | return false |
652 | end |
687 | function AITurnStrategy:startTurn(driveStrategyStraight) |
688 | |
689 | local turnData = driveStrategyStraight.turnData |
690 | |
691 | self.isTurning = true |
692 | |
693 | -- clear turn segments |
694 | for _,segment in pairs(self.turnSegments) do |
695 | if segment.o ~= nil then |
696 | delete(segment.o) |
697 | end |
698 | end |
699 | |
700 | self.turnSegments = {} |
701 | self.turnSegmentsTotalLength = 0 |
702 | self.activeTurnSegmentIndex = 1 |
703 | |
704 | local allowLeft = self.usesExtraStraight == turnData.useExtraStraightLeft and driveStrategyStraight.gabAllowTurnLeft |
705 | local allowRight = self.usesExtraStraight == turnData.useExtraStraightRight and driveStrategyStraight.gabAllowTurnRight |
706 | if not allowLeft and not allowRight then |
707 | return false |
708 | end |
709 | |
710 | --# |
711 | AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, self.vehicle) |
712 | for _,implement in pairs(self.vehicle:getAttachedAIImplements()) do |
713 | AIVehicleUtil.updateInvertLeftRightMarkers(self.vehicle, implement.object) |
714 | end |
715 | |
716 | -- determine turn direction |
717 | local leftAreaPercentage, rightAreaPercentage = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle, turnData) |
718 | |
719 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
720 | log(" --(I)--> self.turnLeft:", self.turnLeft, "leftAreaPercentage:", leftAreaPercentage, "rightAreaPercentage:", rightAreaPercentage) |
721 | end |
722 | |
723 | if driveStrategyStraight.corridorDistance ~= nil then |
724 | self.corridorPositionOffset = -driveStrategyStraight.corridorDistance |
725 | end |
726 | |
727 | local checkForLastValidPosition = function(vehicleNode, turnLeft, threshold) |
728 | if turnLeft and not allowLeft then |
729 | return false |
730 | end |
731 | if not turnLeft and not allowRight then |
732 | return false |
733 | end |
734 | |
735 | local position = driveStrategyStraight.lastValidTurnLeftPosition |
736 | if not turnLeft then |
737 | position = driveStrategyStraight.lastValidTurnRightPosition |
738 | end |
739 | |
740 | if position[1] ~= 0 and position[2] ~= 0 and position[3] ~= 0 then |
741 | local x, y, z = unpack(driveStrategyStraight.lastValidTurnCheckPosition) |
742 | local distance = MathUtil.vector3Length(position[1]-x, position[2]-y, position[3]-z) |
743 | if distance > threshold then |
744 | self.lastValidTurnPositionOffset = -distance |
745 | |
746 | return true |
747 | end |
748 | end |
749 | end |
750 | |
751 | if self.turnLeft == nil then |
752 | local forcePreferLeft = self.collisionEndPosLeft == nil and self.collisionEndPosRight ~= nil |
753 | local forcePreferRight = self.collisionEndPosRight == nil and self.collisionEndPosLeft ~= nil and allowRight and rightAreaPercentage > AIVehicleUtil.VALID_AREA_THRESHOLD |
754 | local preferLeft = ((leftAreaPercentage > rightAreaPercentage or forcePreferLeft) and not forcePreferRight) |
755 | |
756 | if allowLeft and leftAreaPercentage > AIVehicleUtil.VALID_AREA_THRESHOLD and (preferLeft or not allowRight) then |
757 | self.turnLeft = true |
758 | |
759 | -- still check for last valid turn position since the distance to the field could be greater than 5m |
760 | checkForLastValidPosition(self.vehicleDirectionNode, true, 5) |
761 | elseif allowRight and rightAreaPercentage > AIVehicleUtil.VALID_AREA_THRESHOLD then |
762 | self.turnLeft = false |
763 | |
764 | -- still check for last valid turn position since the distance to the field could be greater than 5m |
765 | checkForLastValidPosition(self.vehicleDirectionNode, false, 5) |
766 | else |
767 | if not checkForLastValidPosition(self.vehicleDirectionNode, true, 5) then |
768 | if not checkForLastValidPosition(self.vehicleDirectionNode, false, 5) then |
769 | self:debugPrint("Stopping AIVehicle - no valid ground (I)") |
770 | return false |
771 | else |
772 | self.turnLeft = false |
773 | end |
774 | else |
775 | self.turnLeft = true |
776 | end |
777 | end |
778 | else |
779 | -- first, switch turn direction |
780 | self.turnLeft = not self.turnLeft |
781 | |
782 | -- if we are not allowed to turn into a direction because of field border, collision etc |
783 | -- and we have valid ground on the opposite side, we try the opposite side instead of stopping the ai |
784 | -- this solves bug #32797 |
785 | if self.turnLeft then |
786 | if not allowLeft and rightAreaPercentage > AIVehicleUtil.VALID_AREA_THRESHOLD then |
787 | self.turnLeft = not self.turnLeft |
788 | end |
789 | else |
790 | if not allowRight and leftAreaPercentage > AIVehicleUtil.VALID_AREA_THRESHOLD then |
791 | self.turnLeft = not self.turnLeft |
792 | end |
793 | end |
794 | |
795 | if self.turnLeft then |
796 | if not allowLeft or leftAreaPercentage < AIVehicleUtil.VALID_AREA_THRESHOLD then |
797 | if not checkForLastValidPosition(self.vehicleDirectionNode, true, 5) then |
798 | return false |
799 | end |
800 | else |
801 | -- still check for last valid turn position since the distance to the field could be greater than 5m |
802 | checkForLastValidPosition(self.vehicleDirectionNode, true, 5) |
803 | end |
804 | else |
805 | if not allowRight or rightAreaPercentage < AIVehicleUtil.VALID_AREA_THRESHOLD then |
806 | if not checkForLastValidPosition(self.vehicleDirectionNode, false, 5) then |
807 | return false |
808 | end |
809 | else |
810 | -- still check for last valid turn position since the distance to the field could be greater than 5m |
811 | checkForLastValidPosition(self.vehicleDirectionNode, false, 5) |
812 | end |
813 | end |
814 | end |
815 | |
816 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
817 | log(" --(II)--> self.turnLeft:", self.turnLeft, "leftAreaPercentage:", leftAreaPercentage, "rightAreaPercentage:", rightAreaPercentage) |
818 | end |
819 | |
820 | -- update turn data |
821 | driveStrategyStraight.turnLeft = not self.turnLeft |
822 | driveStrategyStraight:updateTurnData() |
823 | driveStrategyStraight.turnLeft = nil |
824 | |
825 | local checkFrontDistance = 5 |
826 | |
827 | --# finally set new AI direction and target before turn -> if turn gets interrupted the direction afterwards will still be ok |
828 | self.vehicle.aiDriveDirection[1], self.vehicle.aiDriveDirection[2] = -self.vehicle.aiDriveDirection[1], -self.vehicle.aiDriveDirection[2] |
829 | |
830 | local sideOffset |
831 | if self.turnLeft then |
832 | sideOffset = turnData.sideOffsetLeft |
833 | else |
834 | sideOffset = turnData.sideOffsetRight |
835 | end |
836 | |
837 | -- move the ai target by the work width to the turn direction |
838 | local x, z = self.vehicle.aiDriveTarget[1], self.vehicle.aiDriveTarget[2] |
839 | local dirX, dirZ = self.vehicle.aiDriveDirection[1], self.vehicle.aiDriveDirection[2] |
840 | local sideDistance = 2*sideOffset |
841 | local sideDirX, sideDirY = -dirZ, dirX |
842 | x, z = x+sideDirX*sideDistance, z+sideDirY*sideDistance |
843 | |
844 | self.vehicle.aiDriveTarget[1], self.vehicle.aiDriveTarget[2] = x, z |
845 | |
846 | self.vehicle:aiStartTurn(self.turnLeft) |
847 | |
848 | return true |
849 | end |
81 | function AITurnStrategy:update(dt) |
82 | |
83 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
84 | |
85 | AITurnStrategy.drawTurnSegments(self.turnSegments) |
86 | |
87 | if self.maxTurningSizeBoxes ~= nil then |
88 | --if not self.isTurning then |
89 | if table.getn(self.maxTurningSizeBoxes) > 0 then |
90 | self.maxTurningSizeBoxes2 = {} |
91 | for _,box in pairs(self.maxTurningSizeBoxes) do |
92 | local box2 = {} |
93 | box2.points = {} |
94 | box2.name = box.name |
95 | box2.color = {unpack(box.color)} |
96 | |
97 | local x,y,z = box.x,box.y,box.z |
98 | |
99 | local blx = box.xx*box.size[1] - box.zx*box.size[3] |
100 | local blz = box.xz*box.size[1] - box.zz*box.size[3] |
101 | |
102 | local brx = -box.xx*box.size[1] - box.zx*box.size[3] |
103 | local brz = -box.xz*box.size[1] - box.zz*box.size[3] |
104 | |
105 | local flx = box.xx*box.size[1] + box.zx*box.size[3] |
106 | local flz = box.xz*box.size[1] + box.zz*box.size[3] |
107 | |
108 | local frx = -box.xx*box.size[1] + box.zx*box.size[3] |
109 | local frz = -box.xz*box.size[1] + box.zz*box.size[3] |
110 | |
111 | |
112 | table.insert(box2.points, { x+blx, y-box.size[2], z+blz } ) -- lower: lb |
113 | table.insert(box2.points, { x+brx, y-box.size[2], z+brz } ) -- rb |
114 | table.insert(box2.points, { x+frx, y-box.size[2], z+frz } ) -- rf |
115 | table.insert(box2.points, { x+flx, y-box.size[2], z+flz } ) -- lf |
116 | |
117 | table.insert(box2.points, { x+blx, y+box.size[2], z+blz } ) -- upper: lb |
118 | table.insert(box2.points, { x+brx, y+box.size[2], z+brz } ) |
119 | table.insert(box2.points, { x+frx, y+box.size[2], z+frz } ) |
120 | table.insert(box2.points, { x+flx, y+box.size[2], z+flz } ) |
121 | |
122 | table.insert(box2.points, { x, y, z } ) |
123 | |
124 | table.insert(self.maxTurningSizeBoxes2, box2) |
125 | end |
126 | self.maxTurningSizeBoxes = {} |
127 | end |
128 | |
129 | if self.maxTurningSizeBoxes2 ~= nil then |
130 | for _,box2 in pairs(self.maxTurningSizeBoxes2) do |
131 | local p = box2.points |
132 | local c = box2.color |
133 | |
134 | -- bottom |
135 | 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]) |
136 | 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]) |
137 | 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]) |
138 | 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]) |
139 | -- top |
140 | 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]) |
141 | 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]) |
142 | 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]) |
143 | 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]) |
144 | -- left |
145 | 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]) |
146 | 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]) |
147 | -- right |
148 | 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]) |
149 | 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]) |
150 | -- center |
151 | p[9][2] = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, p[9][1],p[9][2],p[9][3]) + 2 |
152 | drawDebugPoint(p[9][1],p[9][2],p[9][3], 1, 0, 0, 1) |
153 | |
154 | Utils.renderTextAtWorldPosition(p[9][1],p[9][2],p[9][3], box2.name, getCorrectTextSize(0.012), 0) |
155 | end |
156 | end |
157 | end |
158 | |
159 | end |
160 | |
161 | end |