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

Дополнительно · 6

Квадрат расстояния

В уроке «Радиус обнаружения» мы сравнивали расстояние с радиусом, а само расстояние считали с квадратным корнем, как в уроке «Расстояние». Оказывается, для такого сравнения корень можно не считать.

Корень нам нужен, чтобы получить само расстояние в пикселях. Но когда мы только спрашиваем «ближе радиуса или дальше», настоящее число ни к чему. Достаточно сравнить квадраты.

Это работает потому, что возведение в квадрат не меняет, какое из двух чисел больше, пока оба не отрицательные. Было 3 < 5, станет 9 < 25. А расстояние и радиус отрицательными не бывают.

Пример: dx равно 3, dy равно 4. С корнем расстояние получается 5, а без корня 3 * 3 + 4 * 4 даёт 25. Возьмём радиус 6. Проверка 5 < 6 верна, и проверка 25 < 36 тоже верна. Ответ один и тот же.

// С корнем: считаем настоящее расстояние и сравниваем его с радиусом.
(dx * dx + dy * dy).sqrt() < radius
// Без корня: сравниваем квадрат расстояния с квадратом радиуса.
dx * dx + dy * dy < radius * radius

Квадратный корень компьютер считает дольше, чем сложение и умножение. Одну проверку это не замедлит: Rust работает быстро, и в наших примерах разницы не заметить. Но если в игре тысячи объектов и каждый кадр для каждого нужна такая проверка, корни складываются во что-то заметное. Поэтому там, где проверок много, расстояния сравнивают в квадрате.

Переписывать так всё подряд не нужно: пока проверок мало, обычное расстояние проще читать.

  • Радиус тоже надо возвести в квадрат. Если сравнить квадрат расстояния с обычным радиусом, получится, что мы сравниваем «квадратные пиксели» с обычными, и ответ будет неверным.
  • Для двух кругов, как в уроке «Пересечение кружков», сравнивать надо с (r1 + r2) * (r1 + r2), а не с r1 * r1 + r2 * r2. Сумму радиусов возводим в квадрат целиком.
  • Если само расстояние нужно, например чтобы поделить на него, как в уроке «Направление», то корень всё равно нужен. Квадраты годятся только для вопроса «ближе или дальше».

Поиск ближайшей цели из урока «Поиск ближайшей цели» тоже можно делать без корня: ближайшая по квадрату расстояния та же мишень, что и ближайшая по самому расстоянию. Достаточно хранить в best_distance квадрат.

Всё работает так же, как в уроке про радиус обнаружения: заведи белый кружок внутрь серого контура — турель покраснеет. Только теперь корня в коде нет. Меняй радиус ниже:

src/main.rs
use macroquad::prelude::*;
// Радиус один и тот же на каждом кадре, поэтому это константа
// (как в уроке «Константы»), а не переменная внутри loop.
const RADIUS: f32 = 300.0;
// Радиус в квадрате. Его тоже достаточно посчитать один раз.
const RADIUS_SQUARED: f32 = RADIUS * RADIUS;
// Своя функция: квадрат расстояния между двумя точками. Это то, что в
// уроке «Расстояние» стояло под корнем, только без самого корня.
fn distance_squared(from_x: f32, from_y: f32, to_x: f32, to_y: f32) -> f32 {
let dx = to_x - from_x;
let dy = to_y - from_y;
dx * dx + dy * dy
}
#[macroquad::main("Квадрат расстояния")]
async fn main() {
loop {
clear_background(BLACK);
// Центр экрана — здесь же стоит турель.
let center_x = screen_width() / 2.0;
let center_y = screen_height() / 2.0;
// Координаты курсора.
let (mouse_x, mouse_y) = mouse_position();
// Контур — радиус обнаружения турели.
draw_circle_lines(center_x, center_y, RADIUS, 3.0, GRAY);
// Турель красная, если игрок внутри радиуса, иначе синяя. Сравниваем
// квадрат расстояния с квадратом радиуса — результат тот же.
if distance_squared(center_x, center_y, mouse_x, mouse_y) < RADIUS_SQUARED {
draw_circle(center_x, center_y, 40.0, RED);
} else {
draw_circle(center_x, center_y, 40.0, BLUE);
}
// Игрок — кружок под курсором.
draw_circle(mouse_x, mouse_y, 25.0, WHITE);
next_frame().await;
}
}
  • distance_squared(...) — своя функция, как в уроке «Свои функции»: считает квадрат расстояния, то есть то, что в уроке «Расстояние» стояло под корнем, но без самого корня.
  • RADIUS_SQUARED — радиус в квадрате. Он, как и сам радиус, не меняется, поэтому достаточно посчитать его один раз, как константу.
  • if distance_squared(...) < RADIUS_SQUARED даёт тот же ответ, что и if distance(...) < RADIUS, но корень не считается.
  • Корень нужен, когда нужно само расстояние. Для вопроса «ближе или дальше» хватает квадратов.