八叉树(Octree)Typescript 实现

export class Octree {

  // 父&子树
  private parent_node: any;
  private children_nodes: Octree[];

  // 原点
  private oringePosition: THREE.Vector3;
  private halfX: number;
  private halfY: number;
  private halfZ: number;

  // 树深度
  public depth: number;

  // 内部实体
  private entities: any[];

  private _all_entities = new Array();
  private _to_update: THREE.Mesh[];
  // 叶子?叶节点
  private _leaves: any;

  private _need_leaves_update: boolean;
  private _need_all_entities_update: boolean;

  private BoxGeo: THREE.Geometry;
  public BoxMesh: THREE.Mesh;

  entities_per_node = 1;
  max_depth = 5;

  constructor(parent: Octree, origin, halfwidth, halfheight, halfdepth) {
    this.oringePosition = origin;
    this.halfX = halfwidth;
    this.halfY = halfheight;
    this.halfZ = halfdepth;

    this.depth = parent === null ? 0 : parent.depth + 1;

    // 设置当前树内无实体
    this.entities = new Array();
    // 父子节点
    this.parent_node = parent;
    this.children_nodes = new Array();

    this._to_update = parent === null ? new Array() : parent._to_update;

    this._leaves = new Array();
    this._leaves.push(this);

    this._need_leaves_update = false;
    this._need_all_entities_update = false;

    // 视觉感受
    this.BoxGeo = new THREE.CubeGeometry(this.halfX * 2, this.halfY * 2, this.halfZ * 2);
    this.BoxMesh = new THREE.Mesh(this.BoxGeo, new THREE.MeshBasicMaterial({color: 0x0, opacity: 1, wireframe: true}));
    this.BoxMesh.position.set(this.oringePosition.clone().x, this.oringePosition.clone().y, this.oringePosition.clone().z);

    if (parent !== null) {
      this.BoxMesh.position.sub(parent.oringePosition);
      parent.BoxMesh.add(this.BoxMesh);
    }
  }

  // 当实体位置改变
  onEntityPoseChanged(entity) {
    if (this._to_update.indexOf(entity) === -1) {
      this._to_update.push(entity);
    }
  }

  // 判断交叉
  intersects(entity) {
    return this.contains(entity.position);
  };

  // 是否包含
  contains(point) {
    let diff = new THREE.Vector3();
    // subVectors方法用来将三维向量的(x,y,z)坐标值分别于参数(a,b)的(x,y,z)相减.并返回新的坐标值的三维向量.
    diff.subVectors(point, this.oringePosition);
    return Math.abs(diff.x) <= this.halfX
      && Math.abs(diff.y) <= this.halfY
      && Math.abs(diff.z) <= this.halfZ;
  };

  // 子节点更新
  needLeavesUpdate() {
    let iter = this;
    while (iter !== null) {
      iter._need_leaves_update = true;
      iter = iter.parent_node;
    }
  };

  // 将实体从当前节点中删除,并将当前this指向根节点
  remove(entity) {
    for (let i = 0; i < this.entities.length; i++) {
      if (this.entities[i] === entity) {
        this.entities.splice(i, 1);
        break;
      }
    }
    // 删除过后将当前this指向根结点
    let iter = this;
    while (iter !== null) {
      iter._need_all_entities_update = true;
      iter = iter.parent_node;
    }
  };

  // 细分
  subdivide() {
    /*       _____________
         /  4   /  5   /   |         y
        /_____ /______/ |  |         |
       /      /      /  |  |         |___ x
      /_____ / _____/   |/ |        /
      |   0  |  1   |  |/7 /       /
      |_____ |_____ |/ | /       z
      |   2  |  3   | |/
      |_____ |_____ |/ (lol)
    */

    if (this.depth >= this.max_depth) {
      return;
    }

    this.needLeavesUpdate();

    let qwidth = this.halfX / 2;
    let qheight = this.halfY / 2;
    let qdepth = this.halfZ / 2;

    this.children_nodes[0] = new Octree(this, new THREE.Vector3(this.oringePosition.x - qwidth,
      this.oringePosition.y + qheight,
      this.oringePosition.z + qdepth),
      qwidth, qheight, qdepth);

    this.children_nodes[1] = new Octree(this, new THREE.Vector3(this.oringePosition.x + qwidth,
      this.oringePosition.y + qheight,
      this.oringePosition.z + qdepth),
      qwidth, qheight, qdepth);

    this.children_nodes[2] = new Octree(this, new THREE.Vector3(this.oringePosition.x - qwidth,
      this.oringePosition.y - qheight,
      this.oringePosition.z + qdepth),
      qwidth, qheight, qdepth);

    this.children_nodes[3] = new Octree(this, new THREE.Vector3(this.oringePosition.x + qwidth,
      this.oringePosition.y - qheight,
      this.oringePosition.z + qdepth),
      qwidth, qheight, qdepth);

    this.children_nodes[4] = new Octree(this, new THREE.Vector3(this.oringePosition.x - qwidth,
      this.oringePosition.y + qheight,
      this.oringePosition.z - qdepth),
      qwidth, qheight, qdepth);

    this.children_nodes[5] = new Octree(this, new THREE.Vector3(this.oringePosition.x + qwidth,
      this.oringePosition.y + qheight,
      this.oringePosition.z - qdepth),
      qwidth, qheight, qdepth);

    this.children_nodes[6] = new Octree(this, new THREE.Vector3(this.oringePosition.x - qwidth,
      this.oringePosition.y - qheight,
      this.oringePosition.z - qdepth),
      qwidth, qheight, qdepth);

    this.children_nodes[7] = new Octree(this, new THREE.Vector3(this.oringePosition.x + qwidth,
      this.oringePosition.y - qheight,
      this.oringePosition.z - qdepth),
      qwidth, qheight, qdepth);
  };

  add(entity) {

    let _this = this;

    function addToThis() {
      let iter = _this;
      while (iter !== null) {
        iter._need_all_entities_update = true;
        iter = iter.parent_node;
      }
      _this.entities.push(entity);
      _this.BoxMesh.visible = true;
    }

    // 如果不包含=>返回
    // 也就是说如果新增的Mesh 不在大Mesh中,不进行查找
    if (!this.intersects(entity)) {
      return;
    }

    if (this.depth >= this.max_depth) {
      addToThis();
    }
    else if (this.children_nodes.length === 0) {
      // ↑小于最大深度&没有子节点并且它里面没有实体的时候
      // ↓每个节点中的数量小于规定要求
      if (this.entities.length < this.entities_per_node) {
        addToThis();
      }
      else {
        // 如果它里面有实体,则拆分
        this.subdivide();
        // 拆分过后,如果内部有实体,则从这个节点中删除,并重新对所有实体做add动作(通过this值的变化)
        if (this.entities.length !== 0) {
          let entities_tmp = this.entities.slice();
          this.entities.length = 0;
          while (entities_tmp.length > 0) {
            let ent = entities_tmp.pop();
            this.remove(ent);
            this.add(ent);
          }
        }
        // 然后再将这个节点添加到指定位置
        this.add(entity);
      }
    }
    else {
      // ↑如果它当前有节点,已经分成八份
      // check if the obb intersects multiple children
      let child_id = -1;
      let multiple_intersect = false;
      for (let i = 0; i < this.children_nodes.length; i++) {
        if (this.children_nodes[i].intersects(entity)) {
          if (child_id !== -1) {
            multiple_intersect = true;
            break;
          }
          child_id = i;
        }
      }
      // 把当前结点放入制定的位置中
      if (multiple_intersect) {
        addToThis();
      }
      else {
        // 放入0节点中
        this.children_nodes[child_id].add(entity);
      }
    }
  }

  empty() {
    if (this.entities.length > 0) {
      return false;
    }

    for (let i = 0; i < this.children_nodes.length; i++) {
      if (!this.children_nodes[i].empty()) {
        return false;
      }
    }
    return true;
  };

  countChildrenIntersections(max, entity) {
    let children_idx = new Array();
    for (let j = 0; j < this.children_nodes.length; j++) {
      if (this.children_nodes[j].intersects(entity)) {
        children_idx.push(j);
      }
      if (children_idx.length === max) {
        break;
      }
    }
    return children_idx;
  }

  // updates children entities reference
  updateChildrenEntities() {
    if (this._need_all_entities_update) {
      this._all_entities.length = 0;
      for (let i = 0; i < this.children_nodes.length; i++) {
        this.children_nodes[i].updateChildrenEntities();
        this._all_entities = this._all_entities.concat(this.children_nodes[i]._all_entities);
      }

      for (let i = 0; i < this.entities.length; i++) {
        this._all_entities.push([this.entities[i], this]);
      }
    }
  }

  // updates leaves reference
  updateLeaves() {
    if (this._need_leaves_update) {
      this._leaves.length = 0;
      for (let i = 0; i < this.children_nodes.length; i++) {

        this.children_nodes[i].updateLeaves();
        this._leaves = this._leaves.concat(this.children_nodes[i]._leaves);
      }

      if (this.children_nodes.length === 0) {
        this._leaves.push(this);
      }

      this._need_leaves_update = false;
    }
  }

  update() {
    let _this = this;
    _this.updateChildrenEntities();
    let entities_tmp = this._all_entities.slice();
    entities_tmp.forEach(function (element) {
      let entity = element[0];

      for (let i = 0; i < _this._to_update.length; i++) {
        if (entity === _this._to_update[i]) {
          let octree;
          let intersections;

          // check if multiple intersection with children
          // if yes do same recursively with parents till we can fit it entirely
          // in one node, and add it to this node
          octree = element[1];
          while (octree !== null) {
            intersections = octree.countChildrenIntersections(2, entity);

            if (intersections.length === 1) {
              // don‘t perform any operation if no update is required
              if (element[1] === octree.children_nodes[intersections[0]]) {
                break;
              }
              element[1].remove(entity);
              octree.children_nodes[intersections[0]].add(entity);
              break;
            }
            else if (octree.parent_node === null && intersections.length > 0) {
              element[1].remove(entity);
              octree.add(entity);
              break;
            }
            else {
              octree = octree.parent_node;
            }
          }
          _this._to_update.splice(i, 1);
          break;
        }
      }
    });

    // update _all_entities arrays
    _this.updateChildrenEntities();

    // get rid of dead leaves
    _this.updateLeaves();

    function pruneUp(node) {
      if (node._all_entities.length <= 1) {
        // remove the children from the leaves array and detach their mesh from parents
        let removeChildrenNodes = function (nodes) {
          for (let i = 0; i < nodes.children_nodes.length; i++) {
            removeChildrenNodes(nodes.children_nodes[i]);
            let idx = _this._leaves.indexOf(nodes.children_nodes[i]);
            if (idx !== -1) {
              _this._leaves.splice(idx, 1);
            }
            nodes.BoxMesh.remove(nodes.children_nodes[i].BoxMesh);
          }
        };

        removeChildrenNodes(node);

        node.needLeavesUpdate();
        node.children_nodes.length = 0;

        if (node._all_entities.length === 1 && (node._all_entities[0])[1] !== node) {
          // if the entity was in a one of the child, put it in current node
          node._all_entities[0][1] = node;	// will update this ref for parents node too
          node.add(node._all_entities[0][0]);
        }
        if (node.parent_node !== null) {
          pruneUp(node.parent_node);
        }
      }
    }

    this._leaves.forEach(function (node) {
      pruneUp(node);
    });
  };

}

  

时间: 2024-10-20 05:54:07

八叉树(Octree)Typescript 实现的相关文章

转:Ogre的八叉树场景管理器OctreeSceneManager

上面是我绘制的一张图. 关于八叉树场景管理器主要需要关注两个类,其一是松散八叉树的数据结构Ogre::Octree,其二是八叉树场景管理器Ogre::OctreeSceneManager. 下面摘录图片中的文字: 松散八叉树的数据结构. 属性:其中mBox为其包围盒,mHalfSize定义为包围盒大小的一半.mChildren是一个大小为8的静态数组,里面保存了8个Octree指针,由八叉树场景管理器创建,由本类管理.mNodes为挂接到当前八叉树上面的八叉树场景节点,mNumNodes保存了挂

转:Ogre源码剖析 - 场景管理之Octree

由于本人的引擎ProjectGaia服务于08年创新杯的游戏项目 – 3D太空游戏,所以理所应当加入Octree(八叉树 – 已经周宁学长发帖介绍过)场景管理器.参考了无数Octree的代码,发现还是我们可爱的Ogre写的最好,于是狂看n千行代码,把精髓提取出来给大家共享. 鉴于我们游戏版教程又n久没有更新了,今天发一篇我对Ogre场景管理器之Octree源代码分析的笔记. 所有代码采用伪代码. 首先回顾一下Ogre场景管理的架构 Ogre以插件形式提供了多种场景管理器 1. BSP管理用于支持

【Mark】四叉树与八叉树

写在前面,四叉树和八叉树就是2D和3D的"二分法",搜索过程与二叉树搜索也类似,二叉树中是将数组sort后存入二叉树中,从而在查找中实现时间复杂度为log2:四叉树/八叉树是按平面/空间范围划分有序node,将所有points(坐标已知)放入所属node中,实现所有points的sort,进而在搜索时,实现时间复杂度为log4/log8    ----WellP.C 转载自: http://blog.csdn.net/zhanxinhang/article/details/670621

KdTree &amp;&amp; Octree 原理学习对比以及可视化分析--&quot;索引树&quot;

1. Kdtree 原理 k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索): 索引结构中相似性查询有两种基本的方式: (1). "范围查询" :给定查询点和查询距离的阈值,从数据集中找出所有与查询点距离小于阈值的数据: (2). "K近邻查询" :K近邻查询是给定查询点及正整数K,从数据集中找到距离查询点最近的"K"个数据,当K=1,则为[最近邻查询]:

Typescript : 遍历Array的方法:for, forEach, every等

方法一,for-of 这个貌似是最常用的方法,angular 2中HTML语法绑定也是要的这种语法. let someArray = [1, "string", false]; for (let entry of someArray) { console.log(entry); // 1, "string", false } for-in 官方文档上强调了for-in和for-of的区别: let list = [4, 5, 6]; for (let i in li

[TypeScript] Create random integers in a given range

Learn how to create random integers using JavaScript / TypeScript. /** * Returns a random int between * @param start inclusive * @param before exclusive */ export function randomInt(start: number, before: number) { return start + Math.floor(Math.rand

TypeScript+vsCode环境搭建

why? 1.基于前面文章的主观意见,所以个人倾向于将mvc的思想也使用到编程工具的使用上.工具嘛,无非是减少必要劳动力,提高工作效率的东西. 2.本人pc机上的vs2012自从装了resharper 之后,从启动速度上就慢了很多,启动一个项目要等待很长时间,且vs中对于ts的智能提示的速度让人难以忍受. start 0.本文是基于nodejs下进行的,没有配置好的同学请自行Google,非本文阐述内容. 1.github下载最新版TypeScript git clone https://git

TypeScript官方文档翻译-5

1.1 Ambient Declarations 1.1 环境声明 An ambient declaration introduces a variable into a TypeScript scope, but has zero impact on the emitted JavaScript program. Programmers can use ambient declarations to tell the TypeScript compiler that some other co

TypeScript系列1-1.5版本新特性

1. 简介 随着PC端快速向移动端迁移,移动(体验)优先的概念也越来越响.由于ReactJS目前移动端仅仅支持iOS,因此移动端Web开发框架只能选择: AngularJS/Angula2 + Ionic框架 + Cordova.想要学习好Angula2以及阅读其代码, 就必须了解和学习TypeScript,也因此需要学习好ES6以及Web Component.近期将开始学习TypeScript语言. 下面先看看TypeScript语言的发展: 鉴于JavaScript这种脚本语言很难应用于大规