# Quadtree

# 四叉树的划分

const WIDTH = Math.min(window.innerWidth, window.innerHeight, 480);

let qtree;

function setup() {
  createCanvas(400, 400);
  let boundary = new Rectangle(200, 200, 200, 200);
  qtree = new QuadTree(boundary, 4);
  for (let i = 0; i < 20; i++) {
    let p = new Point(random(width), random(height));
    qtree.insert(p);
  }
}

function draw() {
  if (mouseIsPressed) {
    let p = new Point(mouseX, mouseY);
    qtree.insert(p);
  }
  background(51);
  qtree.show();
}
QuadTree.js
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Rectangle {
  constructor(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  }

  contains(point) {
    return (
      point.x >= this.x - this.w &&
      point.x < this.x + this.w &&
      point.y >= this.y - this.h &&
      point.y < this.y + this.h
    );
  }
}

class QuadTree {
  constructor(boundary, n) {
    this.boundary = boundary;
    this.capacity = n;
    this.points = [];
    this.divided = false;
  }

  subdivide() {
    let x = this.boundary.x;
    let y = this.boundary.y;
    let w = this.boundary.w;
    let h = this.boundary.h;
    // 右下角 第一象限 ++
    let se = new Rectangle(x + w / 2, y + h / 2, w / 2, h / 2);
    this._southeast = new QuadTree(se, this.capacity);
    // 左下角 第二象限 +-
    let sw = new Rectangle(x - w / 2, y + h / 2, w / 2, h / 2);
    this._southwest = new QuadTree(sw, this.capacity);
    // 左上角 第三象限 --
    let nw = new Rectangle(x - w / 2, y - h / 2, w / 2, h / 2);
    this._northwest = new QuadTree(nw, this.capacity);
    // 右上角 第四象限 -+
    let ne = new Rectangle(x + w / 2, y - h / 2, w / 2, h / 2);
    this._northeast = new QuadTree(ne, this.capacity);
    this.divided = true;
  }

  insert(point) {
    if (!this.boundary.contains(point)) {
      return false;
    }
    if (this.points.length < this.capacity) {
      this.points.push(point);
      return true;
    } else {
      if (!this.divided) {
        this.subdivide();
      }
      if (this._southeast.insert(point)) {
        return true;
      } else if (this._southwest.insert(point)) {
        return true;
      } else if (this._northwest.insert(point)) {
        return true;
      } else if (this._northeast.insert(point)) {
        return true;
      }
    }
  }

  show() {
    stroke(255, 10);
    strokeWeight(1);
    noFill();
    rectMode(CENTER);
    rect(
      this.boundary.x,
      this.boundary.y,
      this.boundary.w * 2,
      this.boundary.h * 2
    );
    if (this.divided) {
      this._southwest.show();
      this._southeast.show();
      this._northwest.show();
      this._northeast.show();
    }
    stroke(255);
    strokeWeight(2);
    this.points.forEach((p) => {
      point(p.x, p.y);
    });
  }
}

# 四叉树的矩形搜索

const WIDTH = Math.min(window.innerWidth, window.innerHeight, 480);

let qtree;

function setup() {
  createCanvas(WIDTH, WIDTH);
  let boundary = new Rectangle(width / 2, height / 2, width / 2, height / 2);
  qtree = new QuadTree(boundary, 4);
  for (let i = 0; i < 1024; i++) {
    let x = randomGaussian(width / 2, width / 8);
    let y = randomGaussian(height / 2, height / 8);
    let p = new Point(x, y);
    qtree.insert(p);
  }
}

function draw() {
  background(51);
  qtree.show();
  let range = new Rectangle(mouseX, mouseY, width / 8, height / 8);
  stroke(0, 255, 0);
  rectMode(CENTER);
  strokeWeight(1);
  rect(range.x, range.y, range.w * 2, range.h * 2);
  let found = qtree.queryRange(range);
  found.forEach((p) => {
    strokeWeight(2);
    point(p.x, p.y);
  });
}
QuadTree.js
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Rectangle {
  constructor(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  }

  contains(point) {
    return (
      point.x >= this.x - this.w &&
      point.x < this.x + this.w &&
      point.y >= this.y - this.h &&
      point.y < this.y + this.h
    );
  }

  intersects(range) {
    return !(
      range.x - range.w > this.x + this.w ||
      range.x + range.w < this.x - this.w ||
      range.y - range.h > this.y + this.h ||
      range.y + range.h < this.y - this.h
    )
  }
}

class QuadTree {
  constructor(boundary, n) {
    this.boundary = boundary;
    this.capacity = n;
    this.points = [];
    this.divided = false;
  }

  subdivide() {
    let x = this.boundary.x;
    let y = this.boundary.y;
    let w = this.boundary.w;
    let h = this.boundary.h;
    // 右下角 第一象限 ++
    let se = new Rectangle(x + w / 2, y + h / 2, w / 2, h / 2);
    this._southeast = new QuadTree(se, this.capacity);
    // 左下角 第二象限 +-
    let sw = new Rectangle(x - w / 2, y + h / 2, w / 2, h / 2);
    this._southwest = new QuadTree(sw, this.capacity);
    // 左上角 第三象限 --
    let nw = new Rectangle(x - w / 2, y - h / 2, w / 2, h / 2);
    this._northwest = new QuadTree(nw, this.capacity);
    // 右上角 第四象限 -+
    let ne = new Rectangle(x + w / 2, y - h / 2, w / 2, h / 2);
    this._northeast = new QuadTree(ne, this.capacity);
    this.divided = true;
  }

  insert(point) {
    if (!this.boundary.contains(point)) {
      return false;
    }
    if (this.points.length < this.capacity) {
      this.points.push(point);
      return true;
    } else {
      if (!this.divided) {
        this.subdivide();
      }
      if (this._southeast.insert(point)) {
        return true;
      } else if (this._southwest.insert(point)) {
        return true;
      } else if (this._northwest.insert(point)) {
        return true;
      } else if (this._northeast.insert(point)) {
        return true;
      }
    }
  }

  queryRange(range, found = []) {
    if( !this.boundary.intersects(range)) {
      // empty array
      return found;
    } else {
      // check point
      this.points.forEach(p => {
        if(range.contains(p)){
          found.push(p)
        }
      })
      // check divide
      if (this.divided) {
        this._northeast.queryRange(range, found);
        this._northwest.queryRange(range, found);
        this._southeast.queryRange(range, found);
        this._southwest.queryRange(range, found);
      }
      return found;
    }
  }

  show() {
    stroke(255, 10);
    strokeWeight(1);
    noFill();
    rectMode(CENTER);
    rect(
      this.boundary.x,
      this.boundary.y,
      this.boundary.w * 2,
      this.boundary.h * 2
    );
    if (this.divided) {
      this._southwest.show();
      this._southeast.show();
      this._northwest.show();
      this._northeast.show();
    }
    stroke(255);
    strokeWeight(2);
    this.points.forEach((p) => {
      point(p.x, p.y);
    });
  }
}

# 四叉树运用在碰撞检测中

const WIDTH = Math.min(window.innerWidth, window.innerHeight, 480);

const particles = [];

function setup() {
  createCanvas(WIDTH, WIDTH);

  for (let i = 0; i < 1024; i++) {
    const particle = new Particle(random(width), random(height), random(2, 4));
    particles.push(particle);
  }
}

function draw() {
  background(51);

  let boundary = new Rectangle(width / 2, height / 2, width / 2, height / 2);
  let qtree = new QuadTree(boundary, 4);

  particles.forEach((p) => {
    const point = new Point(p.x, p.y, p);
    qtree.insert(point);
  });
  particles.forEach((p) => {
    p.move();
    p.render();
    p.setHighlight(false);
  });

  qtree.show();

  // n
  for (let p of particles) {
    let range = new Rectangle(p.x, p.y, 16, 16);
    // log(n)
    let points = qtree.queryRange(range);
    for (let point of points) {
      let other = point.userData;
      if (p !== other && p.intersects(other)) {
        p.setHighlight(true);
      }
    }
  }
}

Particle.js
class Particle {
  constructor(x, y, r = 8) {
    this.x = x;
    this.y = y;
    this.r = r;
    this.highlight = false;
  }

  move() {
    this.x += random(-1, 1);
    this.y += random(-1, 1);
  }

  intersects(other) {
    let d = dist(this.x, this.y, other.x, other.y);
    return d < this.r + other.r;
  }
  setHighlight(value) {
    this.highlight = value;
  }

  render() {
    noStroke();
    if (this.highlight) {
      fill(255);
    } else {
      fill(128);
    }
    ellipse(this.x, this.y, this.r * 2);
  }
}

Quadtree.js
class Point {
  constructor(x, y, userData) {
    this.x = x;
    this.y = y;
    this.userData = userData;
  }
}

class Rectangle {
  constructor(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  }

  contains(point) {
    return (
      point.x >= this.x - this.w &&
      point.x < this.x + this.w &&
      point.y >= this.y - this.h &&
      point.y < this.y + this.h
    );
  }

  intersects(range) {
    return !(
      range.x - range.w > this.x + this.w ||
      range.x + range.w < this.x - this.w ||
      range.y - range.h > this.y + this.h ||
      range.y + range.h < this.y - this.h
    )
  }
}

class QuadTree {
  constructor(boundary, capacity = 4) {
    this.boundary = boundary;
    this.capacity = capacity;
    this.points = [];
    this.divided = false;
  }

  subdivide() {
    let x = this.boundary.x;
    let y = this.boundary.y;
    let w = this.boundary.w;
    let h = this.boundary.h;
    // 右下角 第一象限 ++
    let se = new Rectangle(x + w / 2, y + h / 2, w / 2, h / 2);
    this._southeast = new QuadTree(se, this.capacity);
    // 左下角 第二象限 +-
    let sw = new Rectangle(x - w / 2, y + h / 2, w / 2, h / 2);
    this._southwest = new QuadTree(sw, this.capacity);
    // 左上角 第三象限 --
    let nw = new Rectangle(x - w / 2, y - h / 2, w / 2, h / 2);
    this._northwest = new QuadTree(nw, this.capacity);
    // 右上角 第四象限 -+
    let ne = new Rectangle(x + w / 2, y - h / 2, w / 2, h / 2);
    this._northeast = new QuadTree(ne, this.capacity);
    this.divided = true;
  }

  insert(point) {
    if (!this.boundary.contains(point)) {
      return false;
    }
    if (this.points.length < this.capacity) {
      this.points.push(point);
      return true;
    } else {
      if (!this.divided) {
        this.subdivide();
      }
      if (this._southeast.insert(point)) {
        return true;
      } else if (this._southwest.insert(point)) {
        return true;
      } else if (this._northwest.insert(point)) {
        return true;
      } else if (this._northeast.insert(point)) {
        return true;
      }
    }
  }

  queryRange(range, found = []) {
    if( !this.boundary.intersects(range)) {
      // empty array
      return found;
    } else {
      // check point
      this.points.forEach(p => {
        if(range.contains(p)){
          found.push(p)
        }
      })
      // check divide
      if (this.divided) {
        this._northeast.queryRange(range, found);
        this._northwest.queryRange(range, found);
        this._southeast.queryRange(range, found);
        this._southwest.queryRange(range, found);
      }
      return found;
    }
  }

  show() {
    stroke(255, 10);
    strokeWeight(1);
    noFill();
    rectMode(CENTER);
    rect(
      this.boundary.x,
      this.boundary.y,
      this.boundary.w * 2,
      this.boundary.h * 2
    );
    if (this.divided) {
      this._southwest.show();
      this._southeast.show();
      this._northwest.show();
      this._northeast.show();
    }
    stroke(255);
    strokeWeight(2);
    this.points.forEach((p) => {
      point(p.x, p.y);
    });
  }
}