Я ще не опрацював цілі рівняння для цього, але ось декілька візуалів, які допоможуть обернути голову навколо проблеми. Це зводиться до деякої геометрії:
( Значки автомобілів через Кенні )
З будь-якої заданої початкової точки та орієнтації ми можемо намалювати два кола з нашим мінімальним радіусом повороту - один зліва, а один справа. Вони описують точки на найшвидшому старті нашого шляху.
Ми можемо зробити те ж саме для будь-якого бажаного кінцевого положення та орієнтації. Ці кола описують найкруткіший можливий кінець нашого шляху.
Тепер проблема зводиться до пошуку шляху, що з'єднує один із стартових кіл до одного з кінцевих кіл, цілуючи кожне по його дотичній.
(Це припускаючи, що нам не потрібно обшукувати перешкоди між ними, про що не було сказано в питанні. Відповідь Stormwind потрапляє в те, як ми можемо використовувати інформацію навігаційного графіка для таких типів проблем. Після того, як у нас з'явиться послідовність вузлів щоб пройти, ми можемо застосувати метод нижче до кожного сегменту плану.)
Якщо для простоти використовуємо прямі, ми отримуємо щось подібне:
Це дає нам обмежувальний випадок. Після того, як ви знайдете шлях цим методом, ви можете штучно надути одне або обидва кола початку та кінця, щоб отримати менш прямий, але плавніший шлях, аж до того моменту, коли ці два кола цілуються.
Обчислення цих шляхів
Давайте опрацюємо випадки для одного напрямку повороту - скажімо, ми починаємо наш шлях поворотом праворуч.
Центром нашого правого поворотного кола є:
startRightCenter = carStart.position + carStart.right * minRadius
Назвемо кут прямої ділянки нашого шляху (вимірюється від позитивної осі x) pathAngle
Якщо ми намалюємо вектор від rightCenter
тієї точки, де ми залишаємо коло повороту (в якій точці ми повинні бути звернені до шляхуAngle), то цей вектор є ...
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)
Випадок кросовера складніший - це той, за якого я ще не розробив усю математику. Відповідь я поки що не розміщу, якщо вам це стане в нагоді, поки я опрацюю решту деталей.
Редагувати: призначення всередині мінімального радіуса повороту
Виявляється, цей метод часто працює нестандартно, навіть якщо пункт призначення ближче, ніж наш мінімальний відстань повороту. Принаймні деяка частина одного з кіл повторного входу закінчується поза радіусом повороту, дозволяючи нам знайти життєздатний шлях до тих пір, поки ми не заперечуємо, щоб він став трохи схожим на кренделі ...
Якщо нам не подобається шлях, який ми пройшли таким чином (або якщо це неможливо - я не перевіряв кожен вичерпно кожен випадок - можливо, є неможливі), ми завжди можемо їхати прямо вперед або назад, поки не отримаємо відповідний цілувати контакт між початковим і кінцевим колом, як було показано вище.