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的封装,说实话,我不想接触这方面的知识,但是没办法,该学还要学
室内定位到现在,只是有个大概,还有许多功能需要实现,加油!