ThreeLayer
ThreeLayer在maptalks.three插件里
WARNING
maptalks.three作者是社区人员,不是专职维护人员 ,所以其没有那么多时间天天盯着three插件。
| maptalks-gl | maptalks.three | 备注 | |
|---|---|---|---|
| 诞生时间 | 最近 | 较早 | 一开始maptalks的3d功能主要有three插件提供 |
| 维护 | 官方团队 | 社区人员 | |
| 性质 | 正规军 | 野生的 | |
| 面向的用户 | 所有用户 | 喜欢折腾和捣鼓的用户 | |
| 用户素质要求 | 没有要求 | 对three比较熟悉的二次开发能力的用户 | |
| 侧重点 | 对maptalks核心库的扩展 | 业务里那些花里胡哨的 | |
| 功能 | 矢量切片,倾斜摄影,地形,3d模型,空间分析等 | 业务里那些花里胡哨的 | |
| 上手难度 | 简单,创建图层,传数据配置样式即可 | 中等,需要用户懂three,是么材质啊等 | |
安装
npm i maptalks
npm i maptalks.three或者
<script
type="text/javascript"
src="https://maptalks.com/api/maptalks.min.js"
></script>
<script
type="text/javascript"
src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.js"
></script>
<!-- <script
type="text/javascript"
src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.min.js"
></script> -->使用方式
import { ThreeLayer } from "maptalks.three";
const layer = new ThreeLayer(id, options);WARNING
如果你使用umd包,在页面里用script标签引入,maptalks.three里的所有变量将自动挂载到maptalks命名空间
<script
type="text/javascript"
src="https://maptalks.com/api/maptalks.min.js"
></script>
<script
type="text/javascript"
src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.js"
></script>
<script>
console.log(maptalks);
console.log(maptalks.BaseObject);
const layer = new maptalks.ThreeLayer(id, options);
</script>定位
maptalks.three主要价值是对 maptalks gl生态的补充, 所以当你的业务的需求能用 maptalks-gl 解决时请使用maptalks-gl ,只有当业务里需要自定义图形等时我们在引入 maptalks.three
- 常规的建筑物,行政区,道路,点的图形的功能需求maptalks-gl都是可以完成的
- three插件定位为拥有一定二次开发能力的用户的,其不像 deck.gl那样, 用户配置下参数和样式就可以了,three插件要求你对three比较熟悉
- 当且仅当gl插件不能完成业务需求时在使用 maptalks.three 例如下面这些效果,这些图形都具有强的自定义,且gl插件没有这些功能
![]()
![]()
![]()
关于维护
maptalks.three作者是社区人员,不是专职维护人员 ,所以其没有那么多时间天天盯着three插件。
维护原则:
- 核心bug会第一时间fix
- 不重要的功能和性能等全看作者心情和时间了,不会第一时间去处理的
- three插件里的功能已经基本稳定,不会做大的功能添加
- 专注于bug修复和性能优化,或者开放一些自定义功能的接口等
所以能用maptalks-gl搞定的功能请使用它
使用方式
three插件里主要提供了两个类:
ThreeLayer图层BaseObject图形的基类
ThreeLayer
ThreeLayer是three插件里图形管理的图层,所有的图形都应该有其管理
- 坐标转换,经纬度转webgl坐标
coordinateToVector3 - 米转webgl长度
distanceToVector3 - 海拔米转webgl长度
altitudeToVector3 - 默认图形的构造
toBar,toExtrudePolygon,toModel等 - 图形的添加和删除
addMesh,removeMesh - 其他等
//coordinates to webgl vector3
const vector3 = threeLayer.coordinateToVector3([120, 31]);
//meter to webgl vector3
const vector3 = threeLayer.distanceToVector3(100, 100);
//altitude to webgl vector3
const vector3 = threeLayer.altitudeToVector3(100, 100);其提供了默认图形构造的代理方法:
toExtrudeLinetoExtrudePolygontoBartoModeltoExtrudePolygonstoPathtoFatLinetoLinetoTerraintoHeatMap- 其他等
BaseObject
BaseObject 是three插件里图形的基类,其设计时就尽可能的使其和maptalks的Geometry类似,和Geometry的使用方式基本相同, 比如图形构造,事件监听,气泡绑定等
three插件里提供了常见的功能:
ExtrudePolygonpolygon拔高ExtrudeLine线路拔高Bar立体柱子Path路径FatLine像素线
TIP
这些图形都是基于BaseObject的,都是BaseObject的子类
const bar = threeLayer.toBar(coordinate, options, material);
const line = threeLayer.toExtrudeLine(polyline, options, material);
const bar = three.toBar(coordinate, options, material);常用方法
_createMesh(geometry,material)创建内部的three object3d 对象为Mesh_createGroup()创建内部的three object3d 对象为GroupgetObject3d()获取 内部three原生的object3d对象_initOptions(options)初始化配置信息isVisible()show()hide()setAltitude(altitude)setId(id)getId()
自定义图形
TIP
自定义图形需要你对three比较熟悉,如果你不熟悉three请先熟悉它
从threeLayer的各种图形代理方法,你应该发现自定义图形的规律了
- 基于BaseObject
- 自定义的图形设计如下:
//default values
const OPTIONS = {
radius: 100,
altitude: 0,
};
/**
* custom Circle component
*
* you can customize your own components
* */
class Circle extends maptalks.BaseObject {
constructor(coordinate, options, material, layer) {
options = maptalks.Util.extend({}, OPTIONS, options, {
layer,
coordinate,
});
super();
//Initialize internal configuration
// https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
this._initOptions(options);
const { altitude, radius } = options;
//generate geometry
const r = layer.distanceToVector3(radius, radius).x;
const geometry = new THREE.CircleBufferGeometry(r, 50);
//Initialize internal object3d
// https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L140
this._createMesh(geometry, material);
//set object3d position
const z = layer.altitudeToVector3(altitude, altitude).x;
const position = layer.coordinateToVector3(coordinate, z);
this.getObject3d().position.copy(position);
// this.getObject3d().rotation.x = -Math.PI;
}
}- 构造器的参数规律:
| 参数1 | 参数2 | 参数3 | 参数4 |
|---|---|---|---|
| 坐标或者Geometry | 配置选项 | 材质 | threeLayer |
提示
针对大数量的,three插件里基本都提供了复数形式的图形,比如
ExtrudePolygonsExtrudeLinesBars等
所以当数据量大或者你感觉渲染比较卡时请使用这些方法
three插件里还提供了worker的支持,当图形开启asynchronous选项是时会自动的在worker里去构造图形,以此来解决主线程卡顿 问题
const polygons = threeLayer.toExtrudePolygons(
polygons,
{ asynchronous: true },
material,
);
polygons.on("workerload", (e) => {
//do some things
});WARNING
图形开启worker时,图形构造是异步的,所有如果需要获取图形的一些图形信息,需要在workerload事件里
使用建议
ThreeLayer不要去大量的new,不要频繁的的去add和remove,一个地图上一般只有一个ThreeLayer,因为ThreeLayer里要创建 three的渲染器,相机,场景等对象,这些对象创建还是比较吃资源的,即一个ThreeLayer永远的常驻与地图上,应该通过不断地通过 添加和删除图形来操作
如果你喜欢批量的操作图形,比如图形的批量添加和删除等,你可以:
- 把ThreeLayer看成ThreeScene
- 自己模拟一个虚拟的图层或者组,参考three里的Group来实现图形的批量添加和删除等
这里只是简单的贴个示意代码
class VirtuallyGroup {
constructor(layer) {
this.layer = layer;
this.meshes = [];
}
addMesh(meshes) {
this.meshes = this.meshes.concat(meshes);
this.layer.addMesh(meshes);
return this;
}
removeMesh(meshes) {
//do some things
}
clear() {
//do some things
}
remove() {
//do some things
}
}
const roadGroup = new VirtuallyGroup(threeLayer);
roadGroup.addMesh(roads);
roadGroup.removeMesh(roads);常见问题
初始化TheeLayer图层报错
TheeLayer的初始化是异步的,所以需要再threelayer的prepareToDraw方法里进行业务操作:
- 添加图形
- 场景获取
- 相机获取等
threeLayer.prepareToDraw = function (gl, scene, camera) {
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
camera.add(new THREE.SpotLight(0xffffff, 0.6, 0, Math.PI));
//do some things
};如果希望自己的业务代码只被执行一次,你可以:
threeLayer.prepareToDraw = function (gl, scene, camera) {
if (this._inited) {
return;
}
if (!this._inited) {
this._inited = true;
}
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
camera.add(new THREE.SpotLight(0xffffff, 0.6, 0, Math.PI));
//do some things
};升级three后程序就不正常了
这个是因为three版本迭代时兼容性非常差导致的,three版本升级时会做很多破坏性的更新🤧
WARNING
使用three时切记不要随随便便的升级版本,最好集中性的升级,比如半年或者一年升级一个版本,而不是每次three发版本了都去升级, 目前看three每次版本升级都会做出不少的破坏性更新
原型继承 class的问题
![]()
这种都是你用了three的插件,但是插件比较老,而你用了高版本的three导致的,新版本的three默认打包是 ES6了,但是老的插件继承 时还是用了原型的方法,而ES规范类原型是不能继承class的,所有在你使用时请保持three和其插件版本的一致性
操作ThreeLayer的相机,地图的视角并么有是预期的效果
ThreeLayer 在地图上只是一个图层,地图上可能有多个图层的,所以ThreeLayer里的相机不做开放的,如果想操作相机应该从地图的视角入口 ,而不是想着用一个图层来操作地图,ThreeLayer里的相机只是用来同步地图里的相机而已
TIP
- 相机是个全局性的功能,有地图对象控制
- 所有的图层都是有地图驱动的,不存在用图层来控制地图的可能性,因为地图上有多个图层,如果图层可以控制地图,那么地图会发生紊乱 现象
- 图层应该只控制自己内部的事情,比如图层里的图形能否被点击,图层的显示隐藏,图层里的数据清空,图层开启动画,控制图层内部图形的动画等
three的各种后处理不生效或者不是期望的效果
three插件里的webglcontext默认是有 GroupGLLayer提供的,整个webglcontext是有GroupGLLayer控制的,如果使用three的后处理会 对webglcontext做状态的改变,可能会和GroupGLLayer里对webglcontext的操作产生冲突的,发生一些未知的问题
GroupGLLayer里默认提供一些后处理功能了,如果你需要后处理效果应该从GroupGLLayer入手,比如:bloom,shadow,ssao等
模型拾取性能比较差
three插件里关于模型(gltf,obj,fbx等)的拾取还是用的raycaster,所有当模型体积大了和复杂了会有性能问题, 你可以使用three bvh插件来加速 ,我试了下性能提升还是非常不错的
添加到图层上的图形需要手动动一下地图才渲染出来
threelayer内部还是采用three的那一套渲染机制,需要开启图层的动画渲染函数,实时渲染才行
- 自己手动控制动画函数
function animation() {
// layer animation support Skipping frames
threeLayer._needsUpdate = !threeLayer._needsUpdate;
if (threeLayer._needsUpdate) {
threeLayer.redraw();
}
stats.update();
requestAnimationFrame(animation);
}- 或者开启图层的动画函数选项
var threeLayer = new maptalks.ThreeLayer("t", {
forceRenderOnMoving: true,
forceRenderOnRotating: true,
identifyCountOnEvent: 1,
animation: true,
});animation表示开启动画函数的意思
动画函数表示图层实时渲染,如果你不喜欢这样怕性能浪费,可以自己手动控制渲染时机,在需要重新渲染时调用 图层的 redraw方法即可
threeLayer.redraw();原生的three的Object3D直接加入到 threeLayer会自动bloom
threeLayer里的图形分成两种:
- 基于BaseObject的
- three原生的Object3D
three原生的Object3D加入到threeLayer里,threeLayer也会渲染,但是将脱离threeLayer的全局管理:
- 事件控制
- bloom控制
- 等
建议基于BaseObject构造图形,即使是three原生的Object3D也要用BaseObject包裹下
//default values
var OPTIONS = {
altitude: 0,
};
//https://zhuanlan.zhihu.com/p/199353080
class XXX extends maptalks.BaseObject {
constructor(mesh, options, material, layer) {
options = maptalks.Util.extend({}, OPTIONS, options, { layer });
super();
//Initialize internal configuration
// https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
this._initOptions(options);
this._createGroup();
this.getObject3d().add(mesh);
//Initialize internal object3d
// https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L140
//set object3d position
this.getObject3d().position.copy(mesh.getObject3d().position);
}
}如果是模型的话,threeLayer自带了个方法
baseObjectModel = threeLayer.toModel(model, { coordinate: map.getCenter() });用来包裹模型
toExtrudePolygon 去掉顶部的面
可以使用toExtrudeLine来替代,因为toExtrudePolygon去掉顶部就是围墙,toExtrudeLine就是用来干这个的
function createWall() {
geojson.features.forEach((f) => {
const coordinates = f.geometry.coordinates[0];
const wall = threeLayer.toExtrudeLine(
new maptalks.LineString(coordinates),
{ width: 0, height: 30 },
material1,
);
threeLayer.addMesh(wall);
});
}WARNING
记得将extrudeline的width设为0就是墙了
![]()