Як обчислити шляхи для об'єктів з обмеженим прискоренням?


9

Наприклад, скажіть, що у мене є машина, і автомобіль має певний мінімальний радіус повороту, і я хочу їхати на цьому автомобілі від точки a до точки b, але машина не стоїть на точці b. Як обчислити шлях до точки b? Бути в змозі вказати орієнтацію в точці b також було б добре (скажімо, ви хочете під'їхати до проїжджої дороги, а потім під'їхати до свого гаража - це не дуже добре, якщо ви потрапили на проїжджу частину, проїхавши по своїй газоні і стикаються збоку :)

Вказівник на документацію (або навіть просто ім’я) був би абсолютно чудовим - у мене виникають проблеми взагалі щось знайти.

У моїх спробах вони працюють у простих випадках, але нещадно провалюються в таких ситуаціях, як точка b ближче до мінімального радіуса повороту.

Наприклад, як би ви визначили шлях, подібний до цього (жирний шлях):

Просто вигнутий шлях для ілюстративних цілей

редагувати: У моїй справжній проблемі є деякі прості обмеження в дорозі, але у мене вже є алгоритм A *, який працює, але він дозволяє миттєво змінювати заголовок, тому виглядає нерозумно, коли автомобіль раптом зробить поворот на 90˚ на копійку, коли вони дістаються до точки повороту.


gamedev.stackexchange.com/questions/86881/… але я не впевнений, що розумію відповідь про те, як створити 3d-простір
xaxxon

"в ідеалі цей алгоритм міг би боротися зі зміною швидкості". Чи мінімальний радіус повороту взагалі пов'язаний зі швидкістю, коли він змінюється, чи він постійний для будь-якого одного автомобіля?
DMGregory

Я видалю цю частину. Що я роблю, це більше "sim city", ніж "gran tourismo". Я розумію, чому ви це просите, і я не впевнений, що я думав, коли додав це, оскільки розумію, що це не має значення.
xaxxon

Діаграма кривої Безьє трохи нагадала мені цю відповідь, що стосується також планування траси з обмеженим прискоренням - у такому випадку прискорення моделювалось як напрямлений ракетний рушій, а не радіус повороту, але це все ще може викликати корисні ідеї.
DMGregory

Відповіді:


7

Я ще не опрацював цілі рівняння для цього, але ось декілька візуалів, які допоможуть обернути голову навколо проблеми. Це зводиться до деякої геометрії:

Автомобіль з колами, що вказують на його радіус повороту. ( Значки автомобілів через Кенні )

З будь-якої заданої початкової точки та орієнтації ми можемо намалювати два кола з нашим мінімальним радіусом повороту - один зліва, а один справа. Вони описують точки на найшвидшому старті нашого шляху.

Ми можемо зробити те ж саме для будь-якого бажаного кінцевого положення та орієнтації. Ці кола описують найкруткіший можливий кінець нашого шляху.

Тепер проблема зводиться до пошуку шляху, що з'єднує один із стартових кіл до одного з кінцевих кіл, цілуючи кожне по його дотичній.

(Це припускаючи, що нам не потрібно обшукувати перешкоди між ними, про що не було сказано в питанні. Відповідь 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)

Випадок кросовера складніший - це той, за якого я ще не розробив усю математику. Відповідь я поки що не розміщу, якщо вам це стане в нагоді, поки я опрацюю решту деталей.

Редагувати: призначення всередині мінімального радіуса повороту

Виявляється, цей метод часто працює нестандартно, навіть якщо пункт призначення ближче, ніж наш мінімальний відстань повороту. Принаймні деяка частина одного з кіл повторного входу закінчується поза радіусом повороту, дозволяючи нам знайти життєздатний шлях до тих пір, поки ми не заперечуємо, щоб він став трохи схожим на кренделі ...

Демонстрація варіантів при плануванні шляху до близького пункту призначення.

Якщо нам не подобається шлях, який ми пройшли таким чином (або якщо це неможливо - я не перевіряв кожен вичерпно кожен випадок - можливо, є неможливі), ми завжди можемо їхати прямо вперед або назад, поки не отримаємо відповідний цілувати контакт між початковим і кінцевим колом, як було показано вище.


Це приємний простий спосіб подумати над цим, а дотичні кола в колах досить легко працювати. Досі я лише прокинув вашу відповідь, але одна проблема, яку я застосовував, - це якщо мета всередині поворотних кіл початкової точки.
xaxxon

Найпростіша політика, яку я знаю, для вирішення якої полягає в тому, щоб повернутись до тих пір, поки мета не з’явиться на одному з ваших поворотних кіл, а потім перетворитись на неї. З орієнтацією на місце призначення ви повинні повертати назад, поки круги початку та кінця повороту кудись не цілуються. Я додам схему, щоб візуалізувати цей випадок.
DMGregory

2
Через місяць (і кілька відволікань) я почав це працювати. Я обчислюю 4 дотичні - "зовнішній" і "внутрішній" (або "перетинаючий") дотичні. Тож start.left_circle до Goal.left_circle, start.left_circle "перетнувшись" на Goal.right_circle (а потім два інших просто переключивши кола). Ось "зовнішній" шлях: youtube.com/watch?v=99e5Wm8OKb0, і ось шлях "перетину": youtube.com/watch?v=iEMt8mBheZU
xaxxon

1

Це дуже залежить від решти вашої моделі даних для навігації. Тобто які дані вам зручні, які легко можна додати дані та як ви їх споживаєте.

Взяти аналогічний сценарій із системи руху на воді та з припущенням, що

  • Ви знаходитесь в ігровому циклі
  • у вас є система контуру вузла
  • ваші машини поводяться як автономні об'єкти, які керують собою «зсередини», використовуючи власну силу та рульове управління
  • ваші машини не рухаються, як на рейках

у вас може бути щось на кшталт нижче (вибачте за дитячий вигляд фотографій)

введіть тут опис зображення

(Червоні квадрати - це вузли, червоні лінії - це взаємозв'язки вузлів. Припустимо, ви використовували вирішувач маршрутизації, який давав вузлам 1-9 проїхати через; вузли 4-9, видно на малюнку, і ви хочете пройти через вузли, позначені зеленою лінією , до гаража під номером № 9, проте ви не хочете їхати точно по зеленій лінії, натомість залишайтеся природним чином у правій бічній смузі та робіть плавні маневри).

Кожен вузол матиме метадані, які містять, наприклад, радіус, або декілька, для різних цілей. Один з них - синє коло, яке забезпечує націлювання на автомобілі .

При будь-якому випадку транспортний засіб повинен знати про дві наступні точки вузла P (наступний) та P (наступний + 1) та їх положення. Природно, автомобіль має і своє місце. Автомобіль націлений на правий бічний дотик синього метаданих кола Р (далі). Так що автомобілі їдуть у зворотному напрямку, отже, вони не зіткнуться. Націлювання на дотичну означає, що автомобіль може наближатися до кола з будь-якого напрямку та завжди тримати право. Це приблизний основний принцип, який можна вдосконалити багатьма способами.

P (наступний + 1) потрібен для визначення відстані - коли автомобіль досягне P (наступний) або потрапляє всередину деякого радіусу метаданих, він може регулювати кут нахилу керма залежно від того, наскільки далеко знаходиться P (наступний + 1). Тобто якщо це близько, поверніть багато, якщо далеко, поверніть мало. Мабуть, повинні бути й інші правила та крайові умови, наприклад, розрахунок відстані між автомобілем та довідковою лінією на основі правого бічного дотику P (наступний) та P (наступний + 1), і виправлення цього - заповіт залишитися на пунктирній (вгорі рис) і пунктирній (внизу pic) лінії.

У будь-якому випадку, коли машина проходить один вузол , він це забуває і починає дивитися на наступні два .

До вашого питання. Мабуть, досягнувши вузла 7 (на малюнку вище, який розглядається як вузол 2 на малюнку нижче), він не може повернутись достатньо .

введіть тут опис зображення

Одне можливе рішення - побудувати кілька довідкових ліній і підтримувати ціль весь час , а потім рухатись автомобілем за допомогою власних фізичних налаштувань (прискорюватись із заданою швидкістю, зворотно повільніше, брати до уваги обмеження швидкості метаданих вузла, гальмувати в заданому або обчисленому вигляді Г тощо). Як було сказано, автомобіль є автономним, самоописується, самонесучим об'єктом у цьому сценарії.

Майте зелені довідкові лінії 1,2,3 . Коли автомобіль досягає пурпурового кола , він починає повертати праворуч. На цьому етапі ви вже можете підрахувати, що це не вдасться (ви знаєте максимальну швидкість повороту і можете обчислити криву, і видно, що вона буде перетинати обидві довідкові лінії 2 і 3). Поверніть кермо вправо і дайте йому рухатись вперед (з кроком фізики) і сповільнюйте його, досягаючи довідкової лінії 3 (наближається до - використовуйте порогові значення, f (вказівка ​​на лінію довіри) тощо). Коли вона знаходиться на довідковій лінії 3, перейдіть у зворотний режим, поверніть рульове управління на повне протилежне . Нехай вона повертається назад, поки не досягне довідкової лінії 4(лінія з'єднання між вузлом 1 та 2 - google для "алгоритму точки на лінії"). Повільно, коли вона досягає, знову перейдіть у режим переднього руху , поверніть колесо. Повторіть, поки дорога не буде зрозумілою - мабуть, цього разу вистачило 1 додаткового маневру.

Це загальна ідея: під час ігрового циклу або під час перевірки системи ігор завдання que:

  • Перевірте положення автомобіля, швидкість, кут і т.п. щодо поточних меж та меж потоку ,
  • Якщо ще не досягнуто , продовжуйте робити те, що ви робили (нехай фізика рухатиме його; машина має обороти в хвилину і передачу). Вставте новий чек у вашу систему que, наприклад, за 0,1 с.
  • Якщо буде досягнуто , обчисліть нові умови, встановіть дані та почніть . Вставте новий чек, який відбудеться в системі ке, наприклад, за 0,1 с.
  • Завершіть цикл циклу - продовжуйте, повторюйте.

Надаючи вузли та машини вагомі дані, буде рух та продовження.

Редагувати: І додавати: Це, природно, потребує тонкої настройки. Ваша імітаційна поведінка може зажадати різних довідкових ліній, метаданих, кіл і нічого. Це дало б уявлення про одне можливе рішення.


Мені знадобиться трохи, щоб прочитати вашу відповідь. У мене вже створений і працює загальний набір маршрутів, але він дозволяє об'єктам робити нескінченне прискорення в будь-якій точці.
xaxxon

Випадково у мене насправді є щось досить близьке до того, що ви описуєте. Фіолетова "рухома" лінія повністю процедурно генерується з двох прямих: youtube.com/watch?v=EyhBhrkmRiY, але вона не працює в "жорстких" ситуаціях, і крива не використовується для фактичного проходження маршруту.
xaxxon

0

Я закінчив робити те, що запропонував DMGregory, і це добре працює. Ось якийсь відповідний код (хоча і не окремий), який можна використовувати для обчислення двох стилів дотичних. Я впевнений, що цей код не ефективний, і він, мабуть, навіть не правильний у всіх ситуаціях, але він працює для мене поки що:

bool Circle::outer_tangent_to(const Circle & c2, LineSegment & shared_tangent) const {
    if (this->direction != c2.direction) {
        return false;
    }
    if (this->radius != c2.radius) {
        // how to add it: http://mathworld.wolfram.com/Circle-CircleTangents.html
        // just subtract smaller circle radius from larger circle radius and find path to center
        //  then add back in the rest of the larger circle radius
        throw ApbException("circles with different length radius not supported");
    }

    auto vector_to_c2 = c2.center - this->center;
    glm::vec2 normal_to_c2;
    if (this->direction == Circle::CW) {
        normal_to_c2 = glm::normalize(glm::vec2(-vector_to_c2.y, vector_to_c2.x));
    } else {
        normal_to_c2 = glm::normalize(glm::vec2(vector_to_c2.y, -vector_to_c2.x));
    }

    shared_tangent = LineSegment(this->center + (normal_to_c2 * this->radius),
                                 c2.center + (normal_to_c2 * this->radius));
    return true;
}


bool Circle::inner_tangent_to(const Circle & c2, LineSegment & tangent) const {

    if (this->radius != c2.radius) {
        // http://mathworld.wolfram.com/Circle-CircleTangents.html
        // adding this is non-trivial
        throw ApbException("inner_tangents doesn't support circles with different radiuses");
    }

    if (this->direction == c2.direction) {
        // inner tangents require opposing direction circles
        return false;
    }

    auto vector_to_c2 = c2.center - this->center;
    auto distance_between_circles = glm::length(vector_to_c2);

    if ( distance_between_circles < 2 * this->radius) {
//      throw ApbException("Circles are too close and don't have inner tangents");
        return false;
    } else {
        auto normalized_to_c2 = glm::normalize(vector_to_c2);
        auto distance_to_midpoint = glm::length(vector_to_c2) / 2;
        auto midpoint = this->center + (vector_to_c2 / 2.0f);

        // if hypotenuse is oo then cos_angle = 0 and angle = 90˚
        // if hypotenuse is radius then cos_angle = r/r = 1 and angle = 0
        auto cos_angle = radius / distance_to_midpoint;
        auto angle = acosf(cos_angle);

        // now find the angle between the circles
        auto midpoint_angle = glm::orientedAngle(glm::vec2(1, 0), normalized_to_c2);

        glm::vec2 p1;
        if (this->direction == Circle::CW) {
            p1 = this->center + (glm::vec2{cos(midpoint_angle + angle), sin(midpoint_angle + angle)} * this->radius);
        } else {
            p1 = this->center + (glm::vec2{cos(midpoint_angle - angle), sin(midpoint_angle - angle)} * this->radius);
        }

        auto tangent_to_midpoint = midpoint - p1;
        auto p2 = p1 + (2.0f * tangent_to_midpoint);
        tangent = {p1, p2};

        return true;
    }
};

Ось два фільми цього коду в дії:

Ось "зовнішній" шлях: http://youtube.com/watch?v=99e5Wm8OKb0, і ось шлях "перетинання": http://youtube.com/watch?v=iEMt8mBheZU

Якщо цей код допомагає, але у вас є запитання щодо деяких частин, які тут не показані, просто опублікуйте коментар, і я повинен побачити його через день-два.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.