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

Уроки · 70

Пересечение кружков

В уроках «Радиус обнаружения», «Круглые кнопки» и других проверка была одна и та же: distance(...) < radius — расстояние от точки до центра круга меньше радиуса. Но точка — это, по сути, круг нулевого размера. А что, если оба объекта — настоящие круги, и у каждого свой радиус?

Два круга пересекаются, если расстояние между их центрами меньше суммы их радиусов:

distance(a.x, a.y, b.x, b.y) < a.radius + b.radius

Если бы у b радиус был 0 (то есть b — просто точка), формула превратилась бы ровно в старую: distance(...) < a.radius. Сумма радиусов — это то же самое правило, просто для двух кругов вместо круга и точки.

Раз у нас уже есть struct Circle (как в уроке «Круглые кнопки»), эту проверку удобно повесить на неё методом — тем же приёмом impl, что и в уроке «Методы»:

impl Circle {
fn intersects_with(&self, other: &Circle) -> bool {
distance(self.x, self.y, other.x, other.y) < self.radius + other.radius
}
}

Вызывается он так: a.intersects_with(&b) — читается почти как обычное предложение.

Замени содержимое src/main.rs на это:

src/main.rs
use macroquad::prelude::*;
// Один круг: положение и радиус.
struct Circle {
x: f32,
y: f32,
radius: f32,
}
impl Circle {
// Пересекается ли этот круг с другим — сумма радиусов против расстояния.
fn intersects_with(&self, other: &Circle) -> bool {
distance(self.x, self.y, other.x, other.y) < self.radius + other.radius
}
}
// Своя функция: расстояние между двумя точками, как в уроке «Расстояние».
fn distance(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).sqrt()
}
#[macroquad::main("Пересечение кружков")]
async fn main() {
loop {
clear_background(BLACK);
// Неподвижный круг — всегда в центре экрана.
let still = Circle {
x: screen_width() / 2.0,
y: screen_height() / 2.0,
radius: 120.0,
};
// Круг, который ходит за мышью.
let (mouse_x, mouse_y) = mouse_position();
let moving = Circle { x: mouse_x, y: mouse_y, radius: 80.0 };
// Цвет одинаковый у обоих — по умолчанию жёлтый, меняется на
// красный, как только круги пересеклись.
let mut color = YELLOW;
if moving.intersects_with(&still) {
color = RED;
}
draw_circle(still.x, still.y, still.radius, color);
draw_circle(moving.x, moving.y, moving.radius, color);
next_frame().await;
}
}

Запусти как обычно:

Окно терминала
cargo run

Води мышью вокруг неподвижного круга — оба жёлтые, пока не соприкоснутся, и становятся красными, как только пересекаются. Попробуй сам:

  • distance(...) < radius_a + radius_b — то же сравнение расстояния, что уже было много раз, просто с суммой двух радиусов вместо одного.
  • intersects_with — метод на Circle, а не отдельная функция: self — это тот круг, у которого метод вызвали, other — второй круг, переданный в скобках.
  • &self и &other: &Circle — оба берутся по ссылке, а не в собственность: методу достаточно посмотреть на координаты и радиус, забирать себе сами круги незачем.
  • Оба круга красятся одним и тем же color — проверка одна на двоих, поэтому и результат у них общий.

Эта проверка пригодится в следующем уроке — там будет уже не просто «красный или жёлтый», а настоящее попадание: пуля заденет цель, и обе исчезнут.