Перейти к содержимому

Уроки · 50

Линейная интерполяция

В уроке «Проценты» уже была доля от 0.0 до 1.0 — «сколько от целого». Применим её к отрезку: если доля равна 0, точка окажется точно в начале отрезка, если 1 — точно в конце, а любое число между ними — где-то посередине пути. Такое движение между двумя точками называется линейной интерполяцией, а сокращённо, как обычно говорят в играх и графике — lerp (от английского linear interpolation).

Работает это не только с точками на плоскости. Так можно интерполировать и одно-единственное число (например, яркость света от 0 до 255), и точку в 3D с тремя координатами, и вообще сколько угодно чисел сразу — просто каждое из них по отдельности считается той же формулой. Мы берём пример именно с 2D-точкой потому, что у нас 2D-игра — не потому, что lerp умеет работать только с точками на плоскости.

Пригождается это не только для статичной точки на месте, а для движения. Если на каждом кадре понемногу увеличивать t — например, на 0.02 — объект долетит от start до end ровно за одно и то же число кадров, каким бы длинным ни был путь. Это отличается от привычного движения с постоянной скоростью, как в уроке «Движение»: там чем дальше цель, тем дольше добираться, а через lerp — всегда одинаково по времени, просто на длинных отрезках объект летит быстрее. Так удобно, например, плавно подвинуть UI-элемент или камеру к цели — неважно, далеко она или близко, доедут они за одну и ту же долю секунды.

Для точки (x, y) доля t применяется к каждой координате отдельно, причём совершенно одинаково: к первой координате прибавляется разница между второй координатой и первой, умноженная на t. Для x и y — буквально одна и та же формула, просто с разными числами:

let x = from.x + (to.x - from.x) * t; // x между from.x и to.x, доля t
let y = from.y + (to.y - from.y) * t; // то же самое по y

Двигай ползунок или впиши число — синяя точка едет по отрезку: при t = 0 она в начале, при t = 1 — в конце, а между ними — пропорционально доле пути:

src/main.rs
use macroquad::prelude::*;
// Точка — как в уроке «Точка и прямоугольник».
struct Point {
x: f32,
y: f32,
}
// Линейная интерполяция (lerp): точка на пути от from к to, где t — доля
// пройденного пути, от 0.0 (ещё в from) до 1.0 (уже в to).
fn lerp(from: Point, to: Point, t: f32) -> Point {
Point {
x: from.x + (to.x - from.x) * t,
y: from.y + (to.y - from.y) * t,
}
}
// t одна и та же на каждом кадре, поэтому это константа (как в уроке
// «Константы»), а не переменная внутри loop.
const T: f32 = 0.50;
#[macroquad::main("Линейная интерполяция")]
async fn main() {
loop {
clear_background(BLACK);
// Начало и конец отрезка — по умолчанию на одной высоте.
let start = Point { x: screen_width() * 0.2, y: screen_height() / 2.0 };
let end = Point { x: screen_width() * 0.8, y: screen_height() / 2.0 };
// Сам отрезок.
draw_line(start.x, start.y, end.x, end.y, 4.0, YELLOW);
draw_circle(start.x, start.y, 10.0, WHITE);
draw_circle(end.x, end.y, 10.0, WHITE);
// Точка на отрезке, определяемая t.
let point = lerp(start, end, T);
draw_circle(point.x, point.y, 14.0, SKYBLUE);
next_frame().await;
}
}
  • struct Point { x, y } — снова та же точка, что и в уроке «Точка и прямоугольник».
  • lerp(from, to, t) — берёт две точки и долю t, возвращает точку где-то между ними: при t = 0.0 это from, при t = 1.0 — to.
  • Синяя точка — результат lerp(start, end, t), пересчитывается каждый кадр под текущее значение t.
  • Ползунок и числовое поле управляют одним и тем же t — подвинь один, и другой обновится следом.
  • t в коде — const T, а не переменная внутри loop: значение одно и то же на каждом кадре, пока ты сам не подвинешь ползунок, поэтому это константа, как в уроке «Константы».