ThreeLayer
ThreeLayer
在maptalks.three插件里
WARNING
maptalks.three作者是社区人员,不是专职维护人员 ,所以其没有那么多时间天天盯着three插件。
@maptalks/gl-layers | 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);
其提供了默认图形构造的代理方法:
toExtrudeLine
toExtrudePolygon
toBar
toModel
toExtrudePolygons
toPath
toFatLine
toLine
toTerrain
toHeatMap
- 其他等
BaseObject
BaseObject
是three插件里图形的基类,其设计时就尽可能的使其和maptalks的Geometry
类似,和Geometry
的使用方式基本相同, 比如图形构造,事件监听,气泡绑定等
three插件里提供了常见的功能:
ExtrudePolygon
polygon拔高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插件里基本都提供了复数形式的图形,比如
ExtrudePolygons
ExtrudeLines
Bars
等
所以当数据量大或者你感觉渲染比较卡时请使用这些方法
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() });
用来包裹模型