Я еще не проработал полное уравнение для этого, но вот некоторые наглядные пособия, чтобы помочь нам разобраться в проблеме. Это сводится к некоторой геометрии:
( Авто иконки через Кенни )
Из любой заданной начальной точки и ориентации мы можем нарисовать два круга с минимальным радиусом поворота - один слева, другой справа. Они описывают точки на самом плотном начале нашего пути.
Мы можем сделать то же самое для любой желаемой конечной позиции и ориентации. Эти круги описывают самый жесткий конец нашего пути.
Теперь проблема сводится к тому, чтобы найти путь, который соединяет один из начальных кругов с одним из конечных кругов, целуя каждый по его касательной.
(Это предполагает, что нам не нужно искать пути между препятствиями, что не было упомянуто в вопросе. Ответ Штормграда объясняет, как мы можем использовать информацию навигационного графика для этих типов проблем. Как только мы получим последовательность узлов чтобы пройти, мы можем применить метод ниже для каждого сегмента плана.)
Если для простоты мы используем прямые линии, мы получим что-то вроде этого:
Это дает нам предельный случай. После того, как вы нашли путь с помощью этого метода, вы можете искусственно раздувать один или оба начальных и конечных круга, чтобы получить менее прямой, но более гладкий путь, вплоть до точки, где два круга поцелуи.
Вычисление этих путей
Давайте разработаем случаи для одного направления поворота - скажем, мы начинаем наш путь с поворота направо.
Центр нашего правого поворотного круга:
startRightCenter = carStart.position + carStart.right * minRadius
Давайте назовем угол прямого участка нашего пути (измеренный от положительной оси x) pathAngle
Если мы рисуем вектор из rightCenter
точки, где мы покидаем поворотный круг (в этой точке мы должны смотреть на pathAngle), то этот вектор ...
startOffset = minRadius * (-cos(pathAngle), sin(pathAngle))
Это означает, что точка, где мы покидаем круг, должна быть ...
departure = startRightCenter + startOffset
Точка, в которой мы снова входим в поворот, зависит от того, хотим ли мы закончить поворот налево или направо:
// To end with a right turn:
reentry = endRightCenter + startOffset
// To end with a left turn: (crossover)
reentry = endLeftCenter - startOffset
Теперь, если мы сделали свою работу правильно, линия , соединяющая departure
в reentry
должна быть перпендикулярно startOffset
:
dot(reentry - departure, startOffset) = 0
И решение этого уравнения даст нам угол (ы), под которым это верно. (Я использую множественное число здесь, потому что технически есть два таких угла, но один из них предполагает движение задним ходом, что обычно не то, что мы хотим)
Давайте заменим случай правого поворота на правый поворот в качестве примера:
dot(endRightCenter + startOffset - startRightCenter - startOffset, startOffset) = 0
dot(endRightCenter - startRightCenter, startOffset) = 0
pathAngle = atan2(endRightCenter - startRightCenter)
Случай с кроссовером сложнее - это тот, который я еще не проработал всю математику. Я пока оставлю ответ без ответа, на случай, если он пригодится вам, пока я проработаю оставшиеся детали.
Изменить: пункт назначения внутри минимального радиуса поворота
Оказывается, этот метод часто работает "из коробки", даже когда пункт назначения ближе, чем наше минимальное расстояние поворота. По крайней мере, некоторая часть одного из кругов повторного входа оказывается за пределами радиуса поворота, что позволяет нам найти жизнеспособный путь, если мы не против того, чтобы он стал немного похожим на крендель ...
Если нам не нравится путь, которым мы идем таким образом (или если он неосуществим - я не проверил каждый случай исчерпывающе - возможно, есть невозможные), мы всегда можем двигаться прямо или назад, пока не получим подходящий целующий контакт между начальной и конечной окружностью, как показано выше.