Rust Quadtree

Создание QuadTree с помощью Rust

Великий Админ Ванечка
Rust
QuadTree
GameDev

Всем привет! В этой статье мы попробуем создать Дерево Квадрантов (Quadtree) с помощью языка программирования Rust!

Сперва узнаем, что это такое?

Дерево квадрантов (также квадродерево, 4-дерево, англ. quadtree) — дерево, в котором у каждого внутреннего узла ровно 4 потомка. Деревья квадрантов часто используются для рекурсивного разбиения двухмерного пространства по 4 квадранта (области). Области представляют собой квадраты, прямоугольники или имеют произвольную форм

- wikipedia

Опеределим архитектуру будущего проекта. Нам понадобится:

  • Структура, представляющая собой фигуры (в нашем случае простая безразмерная точка
  • Структура, представляющая собой отдельно взятую ноду.
  • Структура обертка над нодами, представляющая собой удобное api для работы с нодами

Фигуры

Как уже было сказано, фигуры для нас это простые безразмерные точки в 2d пространстве, имеющие только два параметра: координаты в пространстве x, y: u32 Напишем небольшой код структуры:

#[derive(PartialEq, Copy, Clone)]
pub struct Point {
	x: u32,
	y: u32,
}
					
					

И небольшую реализацию с конструктором, создающую новые точки по заданным координатам:

impl Point {
    pub fn new(x: u32, y: u32) -> Self {
        Point {
            x,
            y,
        }
    }
}

Ноды

Теперь напишем код стрктуры для ноды, но для начала выясним, что вообщем нам нужно от каждой ноды?

  • Иметь высоту и ширину соотвественно: width, height
  • Иметь координаты центра ноды x, y
  • Иметь счетчик для подсчета объектов внутри ноды count
  • Иметь счетчик вместимости capacity, после того как нода будет заполнена, она "поделится"
  • Каждая нода должна уметь "делиться", а значит иметь список ссылок на суб-ноды Vec QuadNode
  • Иметь список обьекто (точек), которая нода хранит внутри себя Vec Point
  • Иметь счетчик, который позволит определить уровень ноды level

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

pub struct QuadNode {
    x: u32,
    y: u32,
    width: u32,
    height: u32,
    count: u8,
    capacity: u8,
    child: Vec<Option<Box<QuadNode>>>,
    elements: Vec<Point>, 
    level: u16,
}

И реализацию, вместе с конструктором, создающим новую ноду:

impl QuadNode {
    fn new(x: u32, y: u32, width: u32, height: u32, capacity: u8, level: u16) -> Self {
        QuadNode {
            x,
            y,
            width,
            height,
            count: 0,
            capacity,
            child: Vec::with_capacity(capacity as usize),
            elements: Vec::with_capacity(capacity as usize),
            level,
        }
    }

Первой нашей функцией будет функция contain которая будет возвращать true, если нода содержит точку и false если не содержит. Работа функции заключается в нахождении левых нижних координат, правых верхних и сравнения координат точки с ними. Снова небольшой код:

fn contain(&mut self, p: &Point) -> bool {
    // bottom left
    let botlx = &self.x - (self.width / 2);
    let botly = &self.y - (self.height / 2);
    //top right
    let toprx = &self.x + (self.width / 2);
    let topry = &self.y + (self.height / 2);

    p.x > botlx && p.x < toprx && p.y > botly && p.y < topry
}

Второй нашей функцией, будет функция - split. Как только нода будет заполнена, мы разделим ее на четыре субноды, каждая имеет разный центр:

fn split(&mut self) {
    // center
    let x = &self.x;
    let y = &self.y;
    // child node width && height
    let w = &self.width / 2;
    let h = &self.height / 2;
    // child node level
    let lvl = &self.level + 1;

    println!("subdivade node");

    self.child.push(Some(Box::new(QuadNode::new(x - w / 2, y + h / 2, w, h, self.capacity, lvl))));
    self.child.push(Some(Box::new(QuadNode::new(x + w / 2, y + h / 2, w, h, self.capacity, lvl))));
    self.child.push(Some(Box::new(QuadNode::new(x + w / 2, y - h / 2, w, h, self.capacity, lvl))));
    self.child.push(Some(Box::new(QuadNode::new(x - w / 2, y - h / 2, w, h, self.capacity, lvl))));
}

Следующей функцией будет функция вставки точки внутрь ноды - insert, она будет рекурсивной, если нода содержит субноды, то будем искать субноду содержащую точку и вставлять в нее, а также увеличивать счетчик количества точек внутри ноды. Небольшйо код:

 fn insert(&mut self, p: Point) {
        match self.child.len() {
            0 => {
                if self.contain(&p) {
                    // no child
                    if self.count >= self.capacity {
                        // need to split
                        self.split();
                        self.insert(p);
                    } else {
                        // just push
                        println!("insert new element");
                        self.count += 1;
                        self.elements.push(p);
                    }
                }
            }
            _ => {
                // have child
                for child in self.child.iter_mut().filter_map(Option::as_mut).map(Box::deref_mut) {
                    child.insert(p);
                }
            }
        }
}

Аналогичным образом напишем функцию удаления точки из нодыdelete

fn delete(&mut self, p: Point) {
        match self.child.len() {
            0 => {
                if self.contain(&p) {
                    self.remove(&p);
                    self.count -= 1;
                }
            }
            _ => {
                let mut flag: bool = false;
                for child in self.child.iter_mut().filter_map(Option::as_mut).map(Box::deref_mut) {
                    flag = child.contain(&p);
                }
                if flag {
                    // parent node count -1
                    self.count -= 1;

                    for child in self.child.iter_mut().filter_map(Option::as_mut).map(Box::deref_mut) {
                        child.delete(p);
                    }

                    // need to un_split?
                    if self.count < self.capacity {
                        self.clear();
                    }
                }
            }
        }
}

Закончим работать с нодами, написав две вспомогательные функции. Одну из них вызывает метод delete эта функция удаляет элемент из вектора точек ноды remove, вторая функция clear очищает субноды и добавляет элементы в вектор точек родителя.

fn clear(&mut self) {

        // add child's elements to parent node
        for child in self.child.iter_mut().filter_map(Option::as_mut).map(Box::deref_mut) {
            self.elements.extend(child.elements.iter().cloned()); // append?
        }

        // clear child node
        self.child.clear();
}

fn remove(&mut self, p: &Point) {
        // magic remove
        let pos = self.elements.iter().position(|x| *x == *p).unwrap();
        self.elements.remove(pos);
}
}// Закрывающая скобка impl