Гаразд, у мене все працює, це зайняло назавжди, тому я опублікую тут своє детальне рішення.
Примітка: Усі зразки коду знаходяться в JavaScript.
Тож давайте розбимо проблему на основні частини:
Вам потрібно обчислити довжину, а також точки між 0..1
кривою Безьє
Тепер вам потрібно відрегулювати масштаб свого, 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
, поглянемо:
Ага ... це насправді не рівномірний розподіл балів, чи не так?
Зважаючи на характер кривої Безьє, точки на них 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 кроків:
Обчисліть N
точки на кривій, використовуючи t
та збережіть arc-length
(він же довжина кривої) у цьому положенні в масив
Щоб відобразити T
на t
першу помножити T
на загальну довжину кривої , щоб отримати , u
а потім шукати масив довжин для індексу найбільшого значення , яке меншеu
Якщо ми мали точний хіт, поверніть значення масиву в цьому індексі, розділене 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
на кривій.
Час результатів
В даний час використовуючи, 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);
};
Так і це все! Дякуємо за прочитане;)