Хорошо, у меня все работает, это заняло вечность, поэтому я опубликую свое подробное решение здесь.
Примечание: все примеры кода в 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 Параметризация длины дуги
Так что делать? Говоря простым языком, нам нужна функция для отображения нашего T
на t
кривую, чтобы наши T 0.25
результаты t
были на 25%
длине кривой.
Как мы это делаем? Ну, мы в 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
по кривой :)
Разве это не сложно? Еще раз, оказывается, что простого (хотя и не идеального решения) будет достаточно для игры.
Если вы хотите увидеть полный код, есть доступный Gist:
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;
И вот, корабли теперь плавно движутся по пути.
Если это не работает ...
Когда вы читаете это, все работает отлично и отлично, но у меня изначально были некоторые проблемы с ускорением, когда я объяснял проблему кому-то в чате gamedev, я обнаружил последнюю ошибку в своем мышлении.
Если вы еще не забыли про картинку в исходном вопросе, я упоминаю 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);
};
Так вот и все! Спасибо за чтение ;)