JS實現的四叉樹算法詳解

本文實例講述了JS實現的四叉樹算法。分享給你們供你們參考,具體以下:javascript

最近在看canvas動畫方面教程,裏面提到了採用四叉樹檢測碰撞。以前也看到過四叉樹這個名詞,可是一直不是很懂。因而就又找了一些四叉樹方面的資料看了看,作個筆記,就算往後忘了,也能夠回來看看。html

QuadTree四叉樹顧名思義就是樹狀的數據結構,其每一個節點有四個孩子節點,可將二維平面遞歸分割子區域。QuadTree經常使用於空間數據庫索引,3D的椎體可見區域裁剪,甚至圖片分析處理,咱們今天介紹的是QuadTree最常被遊戲領域使用到的碰撞檢測。採用QuadTree算法將大大減小須要測試碰撞的次數,從而提升遊戲刷新性能,html5

四叉樹很簡單,就是把一塊2d的區域,等分紅4份,以下圖: 咱們把4塊區域從右上象限開始編號, 逆時針。java


四叉樹起始於單節點。對象會被添加到四叉樹的單節點上。node


當更多的對象被添加到四叉樹裏時,它們最終會被分爲四個子節點。(我是這麼理解的:下面的圖片不是分爲四個區域嗎,每一個區域就是一個孩子或子節點)而後每一個物體根據他在2D空間的位置而被放入這些子節點中的一個裏。任何不能正好在一個節點區域內的物體會被放在父節點。(這點我不是很理解,就這幅圖來講,那根節點的子節點豈不是有五個節點了。)算法

8.png

若是有更多的對象被添加進來,那麼每一個子節點要繼續劃分(成四個節點)。數據庫

正如你看到的,每一個節點僅包括幾個物體。這樣咱們就能夠明白前面所說的規則,例如,左上角節點裏的物體是不可能和右下角節點裏的物體碰撞的。因此咱們也就不必運行消耗不少資源的碰撞檢測算法來檢驗他們之間是否會發生碰撞。canvas

下面咱們對四叉樹進行實現:數據結構

主要代碼:(代碼是從整個四叉樹類裏面拷貝出來的,因此帶有this,你們不要無視就好,末尾附有完整的代碼)ide

function QuadTree(boundBox, lvl) {
  var maxObjects = 10;
  this.bounds = boundBox || {
    x: 0,
    y: 0,
    width: 0,
    height: 0
  };
  var objects = [];
  this.nodes = [];
  var level = lvl || 0;
  var maxLevels = 5;
}
複製代碼

maxObjects是每一個節點能容納的最多對象超過 則分割4個節點, 咱們並非事先就分好格子, 而是在插入對象的時候才進行劃分。

maxLevels是 四叉樹的最大層數 超過 則再也不劃分 從根節點開始 最多6 層。

level: 當前層數

objects: 當前節點內的待檢測的對象。

bounds:當前節點所表示的2d區域的範圍

nodes: 4個子節點隊列。

四叉樹每一個節點的面積能夠爲任意形狀。而後,咱們會使用五個四叉樹裏會用到的方法,分別爲:clear,split,getIndex,insert和retrieve。

function clear() {
    objects = [];
    for (var i = 0; i < this.nodes.length; i++) {
      this.nodes[i].clear();
    }
    this.nodes = [];
  };
複製代碼

Clear函數,是經過循環來清除四叉樹全部節點的全部對象。

function split() {
    // Bitwise or [html5rocks]
    var subWidth = (this.bounds.width / 2) | 0;
    var subHeight = (this.bounds.height / 2) | 0;
    this.nodes[0] = new QuadTree({
      x: this.bounds.x + subWidth,
      y: this.bounds.y,
      width: subWidth,
      height: subHeight
    }, level+1);
    this.nodes[1] = new QuadTree({
      x: this.bounds.x,
      y: this.bounds.y,
      width: subWidth,
      height: subHeight
    }, level+1);
    this.nodes[2] = new QuadTree({
      x: this.bounds.x,
      y: this.bounds.y + subHeight,
      width: subWidth,
      height: subHeight
    }, level+1);
    this.nodes[3] = new QuadTree({
      x: this.bounds.x + subWidth,
      y: this.bounds.y + subHeight,
      width: subWidth,
      height: subHeight
    }, level+1);
}
複製代碼

Split 方法,就是用來將節點分紅相等的四份面積,並用新的邊界來初始化四個新的子節點。

function getIndex(obj) {
    var index = -1;
    var verticalMidpoint = this.bounds.x + this.bounds.width / 2;
    var horizontalMidpoint = this.bounds.y + this.bounds.height / 2;
    // Object can fit completely within the top quadrant
    var topQuadrant = (obj.y < horizontalMidpoint && obj.y + obj.height < horizontalMidpoint);
    // Object can fit completely within the bottom quandrant
    var bottomQuadrant = (obj.y > horizontalMidpoint);
    // Object can fit completely within the left quadrants
    if (obj.x < verticalMidpoint &&
        obj.x + obj.width < verticalMidpoint) {
      if (topQuadrant) {
        index = 1;
      }
      else if (bottomQuadrant) {
        index = 2;
      }
    }
    // Object can fix completely within the right quandrants
    else if (obj.x > verticalMidpoint) {
      if (topQuadrant) {
        index = 0;
      }
      else if (bottomQuadrant) {
        index = 3;
      }
    }
    return index;
};
複製代碼

getIndex 方法是個四叉樹的輔助方法,在四叉樹裏,他決定了一個節點的歸屬,經過檢查節點屬於哪一個象限。(最上面第一幅圖不是逆時針在一個面積裏劃分了四塊面積,上面標示了他們的序號,這個方法就是算在一個父節點裏他的子節點的序號)

好比當前區域是Rectange(0, 0, 600, 600) 待檢測矩形是Rectangel(0, 0, 30, 30) 那麼他就在左上象限 index = 1 若是是Rectange(400, 400, 30, 30) 那麼他就在右下象限 index = 3

function insert(obj) {
    if (typeof obj === "undefined") {
      return;
    }
    if (obj instanceof Array) {
      for (var i = 0, len = obj.length; i < len; i++) {
        this.insert(obj[i]);
      }
      return;
    }
    if (this.nodes.length) {
      var index = this.getIndex(obj);
      // Only add the object to a subnode if it can fit completely
      // within one
      if (index != -1) {
        this.nodes[index].insert(obj);
        return;
      }
    }
    objects.push(obj);
    // Prevent infinite splitting
    if (objects.length > maxObjects && level < maxLevels) {
      if (this.nodes[0] == null) {
        this.split();
      }
      var i = 0;
      while (i < objects.length) {
        var index = this.getIndex(objects[i]);
        if (index != -1) {
          this.nodes[index].insert((objects.splice(i,1))[0]);
        }
        else {
          i++;
        }
      }
    }
};
複製代碼

每次插入一個對象 咱們都先看看當前節點有沒有子節點 若是有 咱們就插入子節點。 一直檢測到他沒有子節點爲止 咱們就把對象插入到這個節點, 若是這個節點的對象數量 > 10個 而且當前節點的層數 < MAX_LEVELS 咱們就把節點繼續劃分4個子節點。 而後把當前對象循環 刪除 並插入子節點。若是對象在中心線上,getIndex會返回-1, 因此這些對象會被插入到父節點上面。

一旦對象添加上後,要看看這個節點會不會分裂,能夠經過檢查對象被加入節點後有沒有超過一個節點最大容納對象的數量。分裂起源於節點能夠插入任何對象,這個對象只要符合子節點均可以被加入。不然就加入到父節點。

function retrieve(returnedObjects, obj) {
    if (typeof obj === "undefined") {
      console.log("UNDEFINED OBJECT");
      return;
    }
    var index = this.getIndex(obj);
    if (index != -1 && this.nodes.length) {
      this.nodes[index].findObjects(returnedObjects, obj);
    }
    for (var i = 0, len = objects.length; i < len; i++) {
      returnedObjects.push(objects[i]);
    }
    return returnedObjects;
};
複製代碼

最後一個四叉樹的方法就是 retrieve 方法,他返回了與指定節點可能發生碰撞的全部節點(就是不停尋找與所給節點在一樣象限的節點)。這個方法成倍的減小碰撞檢測數量。

四叉樹的代碼就到這裏爲止了。

完整的代碼以下:

完整的代碼中retrieve就是findObjects。

/** * QuadTree object. * * The quadrant indexes are numbered as below: * | * 1 | 0 * ----+---- * 2 | 3 * | */
function QuadTree(boundBox, lvl) {
  var maxObjects = 10;
  this.bounds = boundBox || {
    x: 0,
    y: 0,
    width: 0,
    height: 0
  };
  var objects = [];
  this.nodes = [];
  var level = lvl || 0;
  var maxLevels = 5;
  /* * Clears the quadTree and all nodes of objects */
  this.clear = function() {
    objects = [];
    for (var i = 0; i < this.nodes.length; i++) {
      this.nodes[i].clear();
    }
    this.nodes = [];
  };
  /* * Get all objects in the quadTree */
  this.getAllObjects = function(returnedObjects) {
    for (var i = 0; i < this.nodes.length; i++) {
      this.nodes[i].getAllObjects(returnedObjects);
    }
    for (var i = 0, len = objects.length; i < len; i++) {
      returnedObjects.push(objects[i]);
    }
    return returnedObjects;
  };
  /* * Return all objects that the object could collide with */
  this.findObjects = function(returnedObjects, obj) {
    if (typeof obj === "undefined") {
      console.log("UNDEFINED OBJECT");
      return;
    }
    var index = this.getIndex(obj);
    if (index != -1 && this.nodes.length) {
      this.nodes[index].findObjects(returnedObjects, obj);
    }
    for (var i = 0, len = objects.length; i < len; i++) {
      returnedObjects.push(objects[i]);
    }
    return returnedObjects;
  };
  /* * Insert the object into the quadTree. If the tree * excedes the capacity, it will split and add all * objects to their corresponding nodes. */
  this.insert = function(obj) {
    if (typeof obj === "undefined") {
      return;
    }
    if (obj instanceof Array) {
      for (var i = 0, len = obj.length; i < len; i++) {
        this.insert(obj[i]);
      }
      return;
    }
    if (this.nodes.length) {
      var index = this.getIndex(obj);
      // Only add the object to a subnode if it can fit completely
      // within one
      if (index != -1) {
        this.nodes[index].insert(obj);
        return;
      }
    }
    objects.push(obj);
    // Prevent infinite splitting
    if (objects.length > maxObjects && level < maxLevels) {
      if (this.nodes[0] == null) {
        this.split();
      }
      var i = 0;
      while (i < objects.length) {
        var index = this.getIndex(objects[i]);
        if (index != -1) {
          this.nodes[index].insert((objects.splice(i,1))[0]);
        }
        else {
          i++;
        }
      }
    }
  };
  /* * Determine which node the object belongs to. -1 means * object cannot completely fit within a node and is part * of the current node */
  this.getIndex = function(obj) {
    var index = -1;
    var verticalMidpoint = this.bounds.x + this.bounds.width / 2;
    var horizontalMidpoint = this.bounds.y + this.bounds.height / 2;
    // Object can fit completely within the top quadrant
    var topQuadrant = (obj.y < horizontalMidpoint && obj.y + obj.height < horizontalMidpoint);
    // Object can fit completely within the bottom quandrant
    var bottomQuadrant = (obj.y > horizontalMidpoint);
    // Object can fit completely within the left quadrants
    if (obj.x < verticalMidpoint &&
        obj.x + obj.width < verticalMidpoint) {
      if (topQuadrant) {
        index = 1;
      }
      else if (bottomQuadrant) {
        index = 2;
      }
    }
    // Object can fix completely within the right quandrants
    else if (obj.x > verticalMidpoint) {
      if (topQuadrant) {
        index = 0;
      }
      else if (bottomQuadrant) {
        index = 3;
      }
    }
    return index;
  };
  /* * Splits the node into 4 subnodes */
  this.split = function() {
    // Bitwise or [html5rocks]
    var subWidth = (this.bounds.width / 2) | 0;
    var subHeight = (this.bounds.height / 2) | 0;
    this.nodes[0] = new QuadTree({
      x: this.bounds.x + subWidth,
      y: this.bounds.y,
      width: subWidth,
      height: subHeight
    }, level+1);
    this.nodes[1] = new QuadTree({
      x: this.bounds.x,
      y: this.bounds.y,
      width: subWidth,
      height: subHeight
    }, level+1);
    this.nodes[2] = new QuadTree({
      x: this.bounds.x,
      y: this.bounds.y + subHeight,
      width: subWidth,
      height: subHeight
    }, level+1);
    this.nodes[3] = new QuadTree({
      x: this.bounds.x + subWidth,
      y: this.bounds.y + subHeight,
      width: subWidth,
      height: subHeight
    }, level+1);
  };
}複製代碼
相關文章
相關標籤/搜索