Рухаючи кораблі між двома планетами вздовж Безьє, пропускаючи деякі рівняння для прискорення


48

Гаразд, я вже опублікував це на math.stackechange.com, але відповіді не отримав :(

Спершу ось картина моєї проблеми, далі опис:

alt текст

Тому я встановив усі бали та значення.

Корабель починає рухатися навколо ліву планети P1з S=0.27 Degreesв gametick, коли вона досягає Point Aпочинається після кривої Безьє , поки вона НЕ досягне Point D, то він подорожує по правій планеті P2з S=0.42 Degreesв гру кліщ. Різниця в Sтому, що подорож з однаковою швидкістю руху навколо планет.

До сих пір добре, я це почав працювати і тепер моя проблема.

Коли S P1і S P2різниться багато, корабель стрибає між двома швидкостями, коли досягає місця призначення, що виглядає досить погано. Тому мені потрібно , щоб прискорити корабель між Point Aі Point Dвід S P1до S P2.

Те, що мені не вистачає, - це фіолетове, це:

  • Спосіб обчислення кліщів, котрий корабель повинен рухатися по Безьє, враховуючи прискорення.

  • І спосіб знайти положення на кривій Безьє на основі Т, знову зважаючи на прискорення.

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


2
Хороша робота над з'ясуванням цього. Я пропоную розмістити свої висновки як відповідь на своє запитання.
bummzack

Відповіді:


83

Гаразд, у мене все працює, це зайняло назавжди, тому я опублікую тут своє детальне рішення.
Примітка: Усі зразки коду знаходяться в JavaScript.

Тож давайте розбимо проблему на основні частини:

  1. Вам потрібно обчислити довжину, а також точки між 0..1кривою Безьє

  2. Тепер вам потрібно відрегулювати масштаб свого, Tщоб прискорити судно з однієї швидкості на іншу

Як правильно зрозуміти Безьє

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

// Part of a class, a, b, c, d are the four control points of the curve
x: function (t) {
    return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
           + 3 * ((1 - t) * (1 - t)) * t * this.b.x
           + 3 * (1 - t) * (t * t) * this.c.x
           + (t * t * t) * this.d.x;
},

y: function (t) {
    return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
           + 3 * ((1 - t) * (1 - t)) * t * this.b.y
           + 3 * (1 - t) * (t * t) * this.c.y
           + (t * t * t) * this.d.y;
}

За допомогою цього тепер можна намалювати криву Безьє, зателефонувавши, xі yз tякого діапазону 0 to 1, поглянемо:

alt текст

Ага ... це насправді не рівномірний розподіл балів, чи не так?
Зважаючи на характер кривої Безьє, точки на них 0...1мають різні arc lenghts, тому відрізки біля початку та кінця довші, ніж ті, що знаходяться біля середини кривої.

Зображення T рівномірно на кривій параметризації довжини дуги AKA

То що робити? Ну в простих термінах нам потрібна функція для відображення OUR Tна tкривій, так що наші T 0.25результати в , tщо по крайней 25%довжини кривої.

Як ми це робимо? Ну, ми Google ... але виявляється, що цей термін не є таким, що можна шукати в Google , і в якийсь момент ви потрапите в цей PDF . Що, звичайно, чудово читати, але у випадку, якщо ви вже забули всі математичні речі, які ви дізналися ще в школі (або просто не любите ці математичні символи), це зовсім марно.

Що тепер? Добре заходьте і Google ще трохи (читайте: 6 годин), і ви нарешті знайдете чудову статтю на цю тему (включаючи приємні фотографії! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingTextToSplines.html

Робити фактичний код

Якщо ви просто не змогли протистояти завантаженню цього PDF-файлу, хоча ви вже давно-давно втратили свої математичні знання (і вам вдалося пропустити чудове посилання на статтю), ви можете подумати: "Боже, це займе сотні рядків коду і тонни процесора "

Ні, не буде. Тому що ми робимо те, що роблять усі програмісти, коли мова йде про математику:
ми просто обманюємо.

Параметризація довжини дуги, ледачий спосіб

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

Отже, як ми мапируємо Tна t? Це просто і складається лише з 3 кроків:

  1. Обчисліть Nточки на кривій, використовуючи tта збережіть arc-length(він же довжина кривої) у цьому положенні в масив

  2. Щоб відобразити Tна tпершу помножити Tна загальну довжину кривої , щоб отримати , uа потім шукати масив довжин для індексу найбільшого значення , яке меншеu

  3. Якщо ми мали точний хіт, поверніть значення масиву в цьому індексі, розділене N, якщо не інтерполюйте трохи між точкою, яку ми знайшли, і наступною, розділіть цю справу ще раз на Nі поверніть.

Це все! Тож тепер давайте розглянемо повний код:

function Bezier(a, b, c, d) {
    this.a = a;
    this.b = b;
    this.c = c;
    this.d = d;

    this.len = 100;
    this.arcLengths = new Array(this.len + 1);
    this.arcLengths[0] = 0;

    var ox = this.x(0), oy = this.y(0), clen = 0;
    for(var i = 1; i <= this.len; i += 1) {
        var x = this.x(i * 0.05), y = this.y(i * 0.05);
        var dx = ox - x, dy = oy - y;        
        clen += Math.sqrt(dx * dx + dy * dy);
        this.arcLengths[i] = clen;
        ox = x, oy = y;
    }
    this.length = clen;    
}

Це ініціалізує нашу нову криву і обчислює arg-lenghts, вона також зберігає останню з довжин як total lengthкривої, ключовим фактором тут є те, this.lenщо є нашим N. Чим вище, тим точніше буде картографування, для кривої розміру на малюнку вище, 100 pointsздається, достатньо, якщо вам просто потрібна хороша оцінка довжини, щось подібне 25вже зробить роботу, будучи лише 1 пікселем у нашому Наприклад, але тоді у вас буде менш точне відображення, яке призведе до не настільки рівномірного розподілу, Tколи їх буде зіставлено t.

Bezier.prototype = {
    map: function(u) {
        var targetLength = u * this.arcLengths[this.len];
        var low = 0, high = this.len, index = 0;
        while (low < high) {
            index = low + (((high - low) / 2) | 0);
            if (this.arcLengths[index] < targetLength) {
                low = index + 1;

            } else {
                high = index;
            }
        }
        if (this.arcLengths[index] > targetLength) {
            index--;
        }

        var lengthBefore = this.arcLengths[index];
        if (lengthBefore === targetLength) {
            return index / this.len;

        } else {
            return (index + (targetLength - lengthBefore) / (this.arcLengths[index + 1] - lengthBefore)) / this.len;
        }
    },

    mx: function (u) {
        return this.x(this.map(u));
    },

    my: function (u) {
        return this.y(this.map(u));
    },

Фактичний код відображення: спочатку робимо простий binary searchна збереженій довжині, щоб знайти найбільшу довжину, меншу за потім targetLength, потім просто повертаємося або робимо інтерполяцію та повернення.

    x: function (t) {
        return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
               + 3 * ((1 - t) * (1 - t)) * t * this.b.x
               + 3 * (1 - t) * (t * t) * this.c.x
               + (t * t * t) * this.d.x;
    },

    y: function (t) {
        return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
               + 3 * ((1 - t) * (1 - t)) * t * this.b.y
               + 3 * (1 - t) * (t * t) * this.c.y
               + (t * t * t) * this.d.y;
    }
};

Знову це обчислюється tна кривій.

Час результатів

alt текст

В даний час використовуючи, mxі myви отримуєте рівномірний розподіл Tна кривій :)

Хіба це не важко, чи не так? Ще раз виявляється, що для гри буде достатньо простого (хоча і не ідеального рішення).

У випадку, якщо ви хочете побачити повний код, вам доступна історія:
https://gist.github.com/670236

Нарешті, прискорення кораблів

Отже, все, що залишилося зараз, - це прискорити кораблі по їх шляху шляхом відображення положення, на Tякому ми потім використовуємо для пошуку tна нашій кривій.

Спочатку нам потрібні два рівняння руху , а саме ut + 1/2at²і(v - u) / t

У фактичному коді, який виглядатиме так:

startSpeed = getStartingSpeedInPixels() // Note: pixels
endSpeed = getFinalSpeedInPixels() // Note: pixels
acceleration = (endSpeed - startSpeed) // since we scale to 0...1 we can leave out the division by 1 here
position = 0.5 * acceleration * t * t + startSpeed * t;

Потім ми масштабуємо це 0...1, виконуючи:

maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;

А там ви йдете, кораблі тепер плавно рухаються по стежці.

У випадку, якщо це не працює ...

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

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

function rotationToMovement(planetSize, rotationSpeed) {
    var r = shipAngle * Math.PI / 180;
    var rr = (shipAngle + rotationSpeed) * Math.PI / 180;
    var orbit = planetSize + shipOrbit;
    var dx = Math.cos(r) * orbit - Math.cos(rr) * orbit;
    var dy = Math.sin(r) * orbit - Math.sin(rr) * orbit;
    return Math.sqrt(dx * dx + dy * dy);
};

Так і це все! Дякуємо за прочитане;)


7
Для перетравлення знадобиться певний час. Але вау, дивовижна відповідь на ваше власне питання.
AttackingHobo

7
Я зареєструвався лише для підтвердження цієї відповіді
Ніхто

Зазначте, мій друже. Працював як шарм. Запитання та відповіді як Запрошені.
Джейс

2
'i' помножується на 0,05, тоді як 'len' встановлено на 100. це 't' буде відображено на '0-5' замість '0-1'.
Злодіяльність

1
@EvilActivity Так, я теж бачив, що його початкова довжина повинна була бути 20, то забула змінити 0,05 на 0,01. Тож краще мати динамічний 'len' (адаптивне до істинної довжини дуги, а може, навіть точно до нього) і обчислити "крок" на 1 / 'len ". Я вважаю це таким дивним, що ніхто більше не підніс це за всі ці роки !!!
Білл Коціас

4

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

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

Вам просто потрібно встановити свої константи: Маса Р1, Р2, корабель

З кожною грою-галочкою (час: t) ви робите 3 речі

  1. Обчисліть силу тяжіння p1 на судні та p2 на кораблі, додайте отримані вектори до вектора тяги.

  2. Обчисліть нову швидкість на основі вашого нового прискорення з кроку 1

  3. Перемістіть корабель відповідно до нової швидкості

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

Якщо вам потрібна допомога з фізики, дайте мені знати.


Я можу подумати про тестування, що якщо ви можете запропонувати спосіб зробити це у межах функції, яка займає t:)
Іво Ветцел

-але в ігровому програмуванні ви не використовуєте t як змінну. Ви вже перебуваєте в параметричній ситуації, тому що ви просто обчислюєте новий dx і dy для корабля. Ось приклад орбіти двох планет (у Flash) aharrisbooks.net/flash/fg2r12/twoPlanets.html - і ось те саме в Python: aharrisbooks.net/pythonGame/ch09/twoPlanets.py
Два пі

2

Я знайшов чудову статтю, яка пояснює можливе рішення цієї проблеми на прикладі коду, написаного на JavaScript. Це працює, "відсуваючи значення t" у потрібному напрямку.

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

На це запитання вже є багато класних відповідей, але я знайшов це рішення, яке варто помітити.


1

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

Не знаю, чи це цілком має сенс, але це, безумовно, мені допомогло.


0

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

Моделюйте кораблі з силою тяги.

Коли кораблі перебувають на останній орбіті на стартовій планеті, прискорюються з повною тягою.

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

Редагувати: Робіть все моделювання одразу, коли вузол збирається покинути орбіту. або надсилати всі дані, або надсилати лише кілька векторів руху з інтервалом та інтерполювати між ними.


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

Я відредагував свою відповідь.
AttackingHobo

0

Якщо я правильно це розумію, ваша проблема надмірно обмежена.

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

Вам доведеться трохи послабити свою проблему, щоб зробити її вирішуваною.


2
Тоді як його розслабити? Те, що я міг собі уявити, - це змінити Т, який я підключаю до матеріалів, що знаходяться в Безьє. Мені потрібно якось змінити масштаб, щоб спочатку повільніше зрости до 0,5, а потім швидше до 1. Отже, корабель сповільнюється від початкової швидкості до фіксованої в середині кривої, а потім знову прискорюється з цієї швидкості до швидкості в кінці кривої?
Іво Ветцель

1
Я думаю, що буде виглядати реалістичніше, якщо космічний корабель прискориться від початкової швидкості до десь біля середини перельоту і потім сповільниться до нової орбіти.
Гарет Різ

І все-таки я застряг у тому, як підключити прискорення до всього, мені потрібно якось змінити T: /
Іво Ветцел

0

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

Незважаючи на те, що MDN стверджує, що це застаріло, ви можете використовувати path.getPointAtLengthправильний результат. https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement/getPointAtLength

В даний час він працює в Chrome / Safari / Firefox і повинен працювати в IE / Edge, але я не перевірив ці 2.


-1

Проблема з прийнятим рішенням

Оскільки Безьє є експоненціальною функцією, ми очікуємо різної швидкості просування в різних областях кривої.

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

Протидія зміщенню шляхом керованого відбору проб

Альтернативним рішенням є лінійно перепризначити , distanceщоб tпісля того, як протидія природний ухил , що функція Безьє виробляє.

Якщо припустити, що ми цього хочемо:

curve length = 10

t      distance
0.2    2
0.4    4
0.6    6
0.8    8
1.0    10

але ось що ми отримуємо від функції Безьє:

t      distance
0.2    0.12
0.4    1.22
0.6    2.45
0.8    5.81
1.0    10.00

Переглядаючи Nвзяті зразки, ми можемо бачити, де дельти відстані найбільші, і повторно проводити («розділити») посередині між двома сусідніми відстанями, збільшуючись Nна 1. Наприклад, розщеплюючи на t=0.9(що знаходиться в середині найбільшої дельти), ми можемо отримати:

0.8    5.81
0.9    7.39
1.0    10.00

Ми повторюємо цей процес протягом наступного найбільшого інтервалу відстані, поки максимальна дельта між будь-якими двома відстанями у всьому наборі не буде деякою minDistanceDelta, а точніше, меншою epsilonвід певних відстаней, на які ми хочемо відобразити кроки t; ми можемо лінійно відобразити бажані tкроки у відповідні distances. Це створює хеш-файл / карту, до якої ви можете дешево отримати доступ, і значення якої ви можете шукати між, під час виконання, без упереджень.

Із збільшенням набору Nвартість їх повторення зростає, тому в ідеалі це робити як попередній процес. Щоразу Nзбільшуючись, додайте два нових результуючі інтервали до intervalsколекції, видаляючи старий, єдиний інтервал, який вони замінили. Це структура, над якою ви працюєте, щоб знайти наступний найбільший інтервал, який розділити на два. Якщо intervalsсортувати відстань, це полегшує роботу, оскільки ви можете просто спливати наступний робочий предмет до кінця та розділити і т.д.

Ми закінчуємо чимось подібним до того, що ми в ідеалі хотіли:

epsilon: 0.01

t            distance
0.200417     2.00417
0.3998132    3.9998132
0.600703     6.00703
0.800001     8.00001
0.9995309    9.995309

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

Потім ви можете отримати, наприклад t=0.5, як це робить Іво у своїй відповіді, тобто шляхом інтерполяції між двома найближчими значеннями вище ( 3.9998132та 6.00703).

Висновок

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

Зауважте, що розділення може бути здійснено стохастично, а не розділенням середини кожного разу, наприклад, ми могли б розділити цей перший приклад на інтервал, t=0.827а не на t=0.9.

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