tree.js和室内定位

1,661 阅读5分钟

1.需求

我们之前室内定位是用openlayers做的2维的平面定位,现在我们想要把它升级成3维的立体定位。我找了很久,决定用tree.js尝试一下。

2. 思路

设想:
1.用建筑方的CAD转成geojson的数据类型,tree.js加载genjsons生成建筑模型
2.创建小人模型,通过动态的改变小人的位置,从而达到定位目的
实际情况:
经过一番尝试可以证明这种方法可行
效果如下:

3.实现方法

1.拿到建筑模型的geojson(这里不展开将)
2.改造一下之前的map类
(1)我们需要动态的添加模型

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

class Map {
  camera = null//相机
  scene = null//场景
  renderer = null//渲染对象
  controls = null
  // fixed是传过来的固定模型,dynamic是各层小人模型,
  constructor(container, fixed = [], ...dynamic) {
    this.fixed = fixed
    this.container = container
    this.dynamic = dynamic
  }

  // 初始化
  init() {
    //
    let container = this.container
    /**
     * 创建场景对象Scene
     */
    this.scene = new THREE.Scene();
    /**
    * 创建网格模型
    */
    //添加静态模型
    if (this.fixed.length > 0) {
      this.fixed.map(item => {
        this.add(item)
      })
    }
    // 添加动态模型,dynamic是个数组,每个数组都是一层小人的模型
    if (this.dynamic.length > 0) {
      this.dynamic.map(item => {
        item.map((items) => {
          this.add(items)
        })
      })
    }
    /**
       * 光源设置
       */
    //点光源
    var point = new THREE.PointLight(0xcccccc); //代表模型的亮度
    point.position.set(0, -600, 200); //点光源位置
    this.scene.add(point); //点光源添加到场景中
    //环境光
    var ambient = new THREE.AmbientLight(0x444444);
    this.scene.add(ambient);
    /**
     * 相机设置
     */
    var width = container.clientWidth; //窗口宽度
    var height = container.clientHeight; //窗口高度
    var k = width / height; //窗口宽高比
    var s = 400; //三维场景显示范围控制系数,系数越大,显示的范围越大
    //创建相机对象 
    // 正向投影
    this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s + 100);
    // 透视投影
    // this.camera = new THREE.PerspectiveCamera(60, k, 1, 1000);
    this.camera.position.set(0, -400, 200); //设置相机位置
    this.camera.lookAt(this.scene.position); //设置相机方向(指向的场景对象)
    /**
     * 创建渲染器对象
     */


    //辅助三维坐标系AxisHelper
    this.axisHelper = new THREE.AxisHelper(250);
    this.scene.add(this.axisHelper);
    this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });

    this.renderer.setSize(width, height); //设置渲染区域尺寸
    // this.renderer.setClearColor(0x4169E1, 1); //设置背景颜色
    this.renderer.setClearColor(0xEEEEEE, 0.0);
    container.appendChild(this.renderer.domElement); //body元素中插入canvas对象
  }

  render = () => {
    // console.log(this.render.toString())

    requestAnimationFrame(this.render); //请求再次执行渲染函数render
    this.renderer.render(this.scene, this.camera);//执行渲染操作
  }
  createControls() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    // this.controls.addEventListener("change", this.test);
  }
  // 添加动态模型
  add(val) {
    this.scene.add(val)
  }
    // 移除模型
  remove(val) {
    this.scene.remove(val)
  }
}
export default Map

(2)在组件中使用

<template>
<div class="home">
  <div id="maps"></div>
</div>
</template>

<script>
import Map from "@/utils/map.js";
export default {
  data() {
    return {
      map: null,
    };
  },
  mounted() {
    this.init();
  },
  methods: {
    init() {
      let container = document.getElementById("maps");
      this.map = new Map(container);
      this.map.init();
      this.map.render();
      this.map.createControls();
    },
  },
};
</script>

<style scoped>
#maps {
  height: 600px;
}
</style>

这时页面上应该是除了辅助坐标轴之外什么也没有,因为我们还没给他传模型,下面我们创建建筑模型和小人 (3)创建建筑模型
为了代码的可读性,我们新建一个文件,model.js,来生成我们需要的模型

// 底图
const base = {
  /**
   * 
   * @param {JSON} json 地图的JSON
   * @param {String} url 模型的贴纸
   * @param {...JSON} jsons 设置内轮廓的JSON
   * @returns {Object}模型的数据
   */
  get(json, url, ...jsons) {
    // 获取底图的面数据
    let mapData = json.geometry.coordinates[0];
    let points = [];
    // 转化为Vector2构成的顶点数组
    mapData.forEach((elem) => {
      // console.log(elem[0]);
      points.push(new THREE.Vector2(elem[0] / 100, elem[1] / 100));
    });
    // 样条曲线生成更多的点
    let SplineCurve = new THREE.SplineCurve(points);
    let shape = new THREE.Shape(SplineCurve.getPoints(300));
    // 遍历打洞的数据
    if (jsons.length > 0) {
      jsons.map(item => {
        let points2 = []
        let mapData2 = item.geometry.coordinates[0];
        mapData2.forEach((elem) => {
          points2.push(new THREE.Vector2(elem[0] / 100, elem[1] / 100));
        });
        // 样条曲线生成更多的点
        let SplineCurve2 = new THREE.SplineCurve(points2);
        let path = new THREE.Path(SplineCurve2.getPoints(300));
        shape.holes.push(path);//设置内轮廓
      })
    }

    let geometry = new THREE.ExtrudeGeometry( //拉伸造型将面拉成体
      shape, //二维轮廓
      //拉伸参数
      {
        amount: 30, //拉伸长度
        bevelEnabled: true, //倒角
      }
    );
    // 加载贴纸
    let texture1 = new THREE.TextureLoader().load(
      url
    );

    // 立即使用纹理进行材质创建
    let material1 = new THREE.MeshBasicMaterial({
      map: texture1,
    });
    // 生成几何体
    let mesh = new THREE.Mesh(geometry, material1); //网格模型对象Mesh
    // 返回几何体数据
    return mesh
  }
}
//导出地图模型
export { base } 

(4)引入地图模型

<template>
<div class="home">
  <div id="maps"></div>
</div>
</template>

<script>
import Map from "@/utils/map.js";
import {
  base
} from "@/utils/map/model.js";
import json from "@/assets/02.json";
import json1 from "@/assets/1.json";
import json2 from "@/assets/2.json";
import json3 from "@/assets/3.json";
import floor2 from "@/assets/floor2.json";
export default {
  data() {
    return {
      map: null,
      // 存放静态模型
      fixed: [],
    };
  },
  mounted() {
    this.init();
  },
  methods: {
    init() {
      //生成固定模型
      this.Fixed();
      let container = document.getElementById("maps");
      this.map = new Map(container, this.fixed);
      this.map.init();
      this.map.render();
      this.map.createControls();
    },
    //  生成固定模型
    Fixed() {
      // 生成第一层
      let ditu = base.get(
        json,
        "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3372825945,1651842093&fm=26&gp=0.jpg",
        json2,
        json1,
        json3
      );
      this.fixed.push(ditu);

      // 生成第二层
      let floors2 = base.get(
        floor2,
        "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2170086043,2766494990&fm=26&gp=0.jpg"
      );
      floors2.position.set(0, 0, 100);
      this.fixed.push(floors2);
      // 生成第三层,二层和三层一样,所以我么采用克隆模型,减少内存大小
      let floors3 = floors2.clone();
      floors3.position.set(0, 0, 200);
      this.fixed.push(floors3);
    },
  },
};
</script>

<style scoped>
#maps {
  height: 600px;
}
</style>

效果入下: (5)创建小人模型
还是在model.js里创建代码如下代码如下:

const person = {
  // 创建球体
  sphereMesh(R, x, y, z, color = 0xDEB887) {
    let geometry = new THREE.SphereGeometry(R, 25, 25); //球体几何体
    let material = new THREE.MeshPhongMaterial({ color: color }); //材质对象Material
    let mesh = new THREE.Mesh(geometry, material); // 创建网格模型对象
    mesh.position.set(x, y, z);
    return mesh;
  },
  // 创建圆柱体
  cylinderMesh(R, h, x, y, z) {
    let geometry = new THREE.CylinderGeometry(R, R, h, 25, 25); //球体几何体
    let material = new THREE.MeshPhongMaterial({ color: 0xDEB887 }); //材质对象Material
    let mesh = new THREE.Mesh(geometry, material); // 创建网格模型对象
    mesh.position.set(x, y, z);
    return mesh;
  },
  // 生成小人模型
  get() {
    // 头部网格模型和组
    let headMesh = this.sphereMesh(10 / 5, 0, 0, 0);
    headMesh.name = "脑壳";
    let leftEyeMesh = this.sphereMesh(1 / 5, 8 / 5, 5 / 5, 4 / 5, 0x000000);
    leftEyeMesh.name = "左眼";
    let rightEyeMesh = this.sphereMesh(1 / 5, 8 / 5, 5 / 5, -4 / 5, 0x000000);
    rightEyeMesh.name = "右眼";
    let headGroup = new THREE.Group();
    headGroup.name = "头部";
    headGroup.add(headMesh, leftEyeMesh, rightEyeMesh);
    // // 身体网格模型和组
    let neckMesh = this.cylinderMesh(3 / 5, 10 / 5, 0, -15 / 5, 0);
    neckMesh.name = "脖子";
    let bodyMesh = this.cylinderMesh(14 / 5, 30 / 5, 0, -35 / 5, 0);
    bodyMesh.name = "腹部";
    let leftLegMesh = this.cylinderMesh(4 / 5, 60 / 5, 0, -80 / 5, -7 / 5);
    leftLegMesh.name = "左腿";
    let rightLegMesh = this.cylinderMesh(4 / 5, 60 / 5, 0, -80 / 5, 7 / 5);
    rightLegMesh.name = "右腿";
    let legGroup = new THREE.Group();
    legGroup.name = "腿";
    legGroup.add(leftLegMesh, rightLegMesh);
    let bodyGroup = new THREE.Group();
    bodyGroup.name = "身体";
    bodyGroup.add(neckMesh, bodyMesh, legGroup);
    // 人Group
    let personGroup = new THREE.Group({ scale: 0.5 });
    personGroup.name = "人";
    personGroup.add(headGroup, bodyGroup);
    // personGroup.translateY(50);沿着Y轴平移
    // personGroup.rotateY(90) 绕Y轴旋转
    return personGroup
  }
}
export { person, base } 

不必在意小人模型的代码,这不是重点 (6)在组件引入小人模型,模拟位置变化

<template>
<div class="home">
  <button @click="addPerson">新增模型</button>
  <button @click="removePerson">删除模型</button>
  <div id="maps"></div>
</div>
</template>

<script>
import Map from "@/utils/map.js";
import {
  base,
  person
} from "@/utils/map/model.js";
import json from "@/assets/02.json";
import json1 from "@/assets/1.json";
import json2 from "@/assets/2.json";
import json3 from "@/assets/3.json";
import floor2 from "@/assets/floor2.json";
export default {
  data() {
    return {
      map: null,
      // 存放静态模型
      fixed: [],
      // 模拟小人的数量
      listData: [...new Array(1).keys()],
      // 第一层小人
      floor1Data: [],
      // 第二层小人
      floor2Data: [],
      // 第三层小人
      floor3Data: [],
      // 小人模型
      personModel: null,
      date: null,
    };
  },
  created() {
    this.date = new Date();
  },
  mounted() {
    this.init();
    this.getList();
    console.log(`加载用了${new Date() - this.date}`);
  },
  methods: {
    init() {
      //生成固定模型
      this.Fixed();
      // 生成动态模型
      this.Dynamic();
      let container = document.getElementById("maps");
      this.map = new Map(
        container,
        this.fixed,
        this.floor1Data,
        this.floor2Data,
        this.floor3Data
      );
      this.map.init();
      this.map.render();
      this.map.createControls();
    },

    // 模拟定时跟新小人位置
    getList() {
      setInterval(() => {
        this.floor1Data.map((item) => {
          // 设置小人位置X,Y,Z
          item.position.set(
            Math.floor(Math.random() * 100 + 1),
            Math.floor(Math.random() * 80 + 1),
            50
          );
        });

        this.floor2Data.map((item) => {
          item.position.set(
            Math.floor(Math.random() * 80 + 1),
            Math.floor(Math.random() * 60 + 1),
            150
          );
        });

        this.floor3Data.map((item) => {
          item.position.set(
            Math.floor(Math.random() * 80 + 1),
            Math.floor(Math.random() * 60 + 1),
            250
          );
        });
      }, 2000);
    },
    //  生成固定模型
    Fixed() {
      // 生成第一层
      let ditu = base.get(
        json,
        "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3372825945,1651842093&fm=26&gp=0.jpg",
        json2,
        json1,
        json3
      );
      this.fixed.push(ditu);

      // 生成第二层
      let floors2 = base.get(
        floor2,
        "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2170086043,2766494990&fm=26&gp=0.jpg"
      );
      floors2.position.set(0, 0, 100);
      this.fixed.push(floors2);
      // 生成第三层,二层和三层一样,所以我么采用克隆模型,减少内存大小
      let floors3 = floors2.clone();
      floors3.position.set(0, 0, 200);
      this.fixed.push(floors3);
    },
    // 生成动态模型
    Dynamic() {
      // 生成小人模型,这里只生成一个,采取克隆的模式复制小人,即使你创建再多小人,内存大小不变,类似对象的浅拷贝,
      this.personModel = person.get();
      // 将小人绕X轴旋转45度
      this.personModel.rotateX(45.5);
      this.listData.map((item, index) => {
        // 这里为每层克隆小人对象
        let a = this.personModel.clone();
        let b = this.personModel.clone();
        let c = this.personModel.clone();
        this.floor1Data.push(a);
        this.floor2Data.push(b);
        this.floor3Data.push(c);
      });
    },
    addPerson() {
      let a = this.personModel.clone();
      a.position.set(
        Math.floor(Math.random() * 100 + 1),
        Math.floor(Math.random() * 80 + 1),
        50
      );
      this.floor1Data.push(a);
      this.map.add(a);
    },
    removePerson() {
      this.map.remove(this.floor1Data[0]);
      this.floor1Data.splice(0, 1);
    },
  },
};
</script>

<style scoped>
#maps {
  height: 600px;
}
</style>

(7)动态的添加小人

  addPerson() {
      let a = this.personModel.clone();
      a.position.set(
        Math.floor(Math.random() * 100 + 1),
        Math.floor(Math.random() * 80 + 1),
        50
      );
      this.floor1Data.push(a);
      this.map.add(a);
    },

(8)删除小人

 removePerson() {
      this.map.remove(this.floor1Data[0]);
      this.floor1Data.splice(0, 1);
    },

总结

这里总结一下tree.js常用的方法
1.克隆(几何体都可以克隆)
mesh.clone()
2.旋转 绕 X Y Z轴旋转
mesh.rotateX(45.5);
3.改变位置 X Y Z
mesh.position.set(0, 0, 200);
4.添加几何体
this.scene.add(val)
5.移除几何体
this.scene.remove(val)
tree.js,只是对webGL的封装,说实话,我不想接触这方面的知识,但是没办法,该学还要学
室内定位到现在,只是有个大概,还有许多功能需要实现,加油!