上一篇已经实现了在mapbox-v50即以后的版本添加threejs,但是他是18年10月之后的事了,那么如何在之前的版本实现呢?因为对已有的项目如果盲目的去修改版本会导致很多以前的内容无法实现。虽然我不知道我这个方法能适用到多少版本,但是至少这是一个思路。使用的是v48.当添加threebox时发生报错。说明无法使用threebox。
那么如何添加呢?
我想大家非常清楚mapbox的map和threejs的场景都是渲染在canvas上的。只要把他们都放在屏幕上就能显示。仔细一想,当然是以map作为最底层的,而要在map上去绘制threejs,而且使threejs图层透明。但是这样会导致一个非常严重的问题mapbox图层被透明的threejs图层覆盖,就无法操控mapbox图层。那么如何解决这一问题呢?
打开map的div,我们会发现里面有好多模块
其中我们知道canvas应该是绘制地图以及图层的部分,而control,顾名思义就是控制地图的操控的部分,所以我们可以在这两个div中间来添加我们的threejs绘制的图层这样对于mapbox的地图操控会任然有效。
var div = document.createElement("div");//创建div
div.id = "ThreeJS";//设置div属性
div.style.position = "absolute";
div.style.zIndex = "101";
//获取mapbox-gl需要添加的class属性
var Todiv = document.getElementsByClassName("mapboxgl-canvas-container mapboxgl-interactive mapboxgl-touch-drag-pan mapboxgl-touch-zoom-rotate");
Todiv[0].appendChild(div);//添加到mapbox-gl div中去
这样我们就把threejs图层放入了地图中去,你以为到这就结束了?不,接下来还有许多步骤,首先我们要获取和mapbox一样的投影方式,这样我们才能正确的通过经纬度来添加我们的threejs模型。应为threejs用的是xyz坐标系,而mapbox用的是地理投影坐标系。
这里我参考了threejs源码的方法,我们先初始化threejs方法。
// 初始化
init:function () {
//创建场景
this.renderer = new THREE.WebGLRenderer({
antialias:true,//开启抗锯齿
alpha:true,//设置成透明
});
this.renderer.shadowMap.enable = true;
this.renderer.autoClear = false;
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.000001, 5000000000);//设置为远景相机
this.renderer.setSize(window.innerWidth,window.innerHeight);//设置渲染大小
this.container = document.getElementById( 'ThreeJS' );//链接到div
this.container.appendChild( this.renderer.domElement );
this.world = new THREE.Group();
this.scene.add(this.world);//场景添加world
this.cameraSynchronizer = new CameraSync( map,this.camera,this.world);
//raycaster = new THREE.Raycaster();
startTime = Date.now();//初始化时间种子
},
在这里我们初始化了threejs的主要对象,与threebox不同的是连接方式,应为mapbox的版本不一样。以我们直接用threejs的连接方式,连接到我们之前创建的div。然后创建一个world的Group对象,用来存放我们的创建的各种obj对象,就像他的名字一样叫做世界。之后最难理解的是cameraSynchronize这个对象。它新建了一个CameraSync对象,而这个CamreaSync对象就是实现让threejs我们的操作与mapbox关联的方法。
threejs里面我想大家都用过相机的操作而且threejs本身也也提供了多种相机控件,但是没有一个是符合我们平时使用和操作地图的方式。对于地图的操作方式是对map移动事件的监听,然后对相机以及地理事物都进行仿射变换。
//相机的操作
function CameraSync(map,camera,world)
{
this.map = map;
this.camera = camera;
this.active = true;
this.camera.matrixAutoUpdate = false;//静止后使用自身的变换矩阵
this.world = world || new THREE.Group();
this.world.position.x = this.world.position.y = ThreeboxConstants.WORLD_SIZE/2;
this.world.matrixAutoUpdate = false;
var _this = this;
this.map.on('move', function() {//当地图移动时改变相机
_this.updateCamera()
});
this.updateCamera();
}
CameraSync.prototype = {//CameraSync对象的方法
updateCamera:function (ev) {
if(!this.camera) {
console.log('nocamera')
return;
}
// Build a projection matrix, paralleling the code found in Mapbox GL JS
//构建一个投影矩阵,与mapbox gl js中的代码并行
const fov = 0.6435011087932844;
var cameraToCenterDistance = 0.5 / Math.tan(fov / 2) * this.map.transform.height;
const halfFov = fov / 2;
const groundAngle = Math.PI / 2 + this.map.transform._pitch;
const topHalfSurfaceDistance = Math.sin(halfFov) * cameraToCenterDistance / Math.sin(Math.PI - groundAngle - halfFov);
// Calculate z distance of the farthest fragment that should be rendered.
const furthestDistance = Math.cos(Math.PI / 2 - this.map.transform._pitch) * topHalfSurfaceDistance + cameraToCenterDistance;
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`当一个片段的距离正好是“进一步的距离”时,再加一点以避免精度问题。
const farZ = furthestDistance * 1.01;
this.camera.projectionMatrix = makePerspectiveMatrix(fov, this.map.transform.width / this.map.transform.height, 1, farZ);
var cameraWorldMatrix = new THREE.Matrix4();
var cameraTranslateZ = new THREE.Matrix4().makeTranslation(0,0,cameraToCenterDistance);
var cameraRotateX = new THREE.Matrix4().makeRotationX(this.map.transform._pitch);
var cameraRotateZ = new THREE.Matrix4().makeRotationZ(this.map.transform.angle);
// Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix
// If this is applied directly to the projection matrix, it will work OK but break raycasting
//与Mapbox GL JS相机不同,将相机的平移和旋转分离到其世界矩阵中。
//如果直接应用于投影矩阵,它会正常工作,但会破坏光线投射
cameraWorldMatrix
.premultiply(cameraTranslateZ)
.premultiply(cameraRotateX)
.premultiply(cameraRotateZ);
this.camera.matrixWorld.copy(cameraWorldMatrix);//对象的全局变换
var zoomPow = this.map.transform.scale;
// Handle scaling and translation of objects in the map in the world's matrix transform, not the camera
//处理世界矩阵变换中地图中对象的缩放和平移,而不是相机
var scale = new THREE.Matrix4;
var translateCenter = new THREE.Matrix4;
var translateMap = new THREE.Matrix4;
var rotateMap = new THREE.Matrix4;
scale
.makeScale(zoomPow, zoomPow , zoomPow );//通过地图的缩放设置对象的缩放
//平移变换
translateCenter
.makeTranslation(ThreeboxConstants.WORLD_SIZE/2, -ThreeboxConstants.WORLD_SIZE / 2, 0);
translateMap
.makeTranslation(-this.map.transform.x, this.map.transform.y , 0);
//弧度旋转
rotateMap
.makeRotationZ(Math.PI);
this.world.matrix = new THREE.Matrix4;
this.world.matrix
.premultiply(rotateMap)
.premultiply(translateCenter)
.premultiply(scale)
.premultiply(translateMap)
//need to reset renderer to avoid paintprogram error需要重置渲染器以避免绘制程序错误
// utils.prettyPrintMatrix(this.camera.projectionMatrix.elements);
}
};
// 生成透视矩阵
function makePerspectiveMatrix(fovy, aspect, near, far) {
var out = new THREE.Matrix4();
var f = 1.0 / Math.tan(fovy / 2),
nf = 1 / (near - far);
var newMatrix = [
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (far + near) * nf, -1,
0, 0, (2 * far * near) * nf, 0
]
out.elements = newMatrix
return out;
};
理解完这部分后我们就将初始化的部分完成,接下来我们可以添加我们的对象,这里我根据自己需求进行了初步的封装,有自身需求的小伙伴可以按照自身需求分装。
// 投影到世界
projectToWorld:function (coords) {
// Spherical mercator forward projection, re-scaling to WORLD_SIZE球面墨卡托正投影,重新缩放到世界尺寸
var projected = [
-ThreeboxConstants.MERCATOR_A * coords[0] * ThreeboxConstants.DEG2RAD * ThreeboxConstants.PROJECTION_WORLD_SIZE,
-ThreeboxConstants.MERCATOR_A * Math.log(Math.tan((Math.PI*0.25) + (0.5 * coords[1] * ThreeboxConstants.DEG2RAD))) * ThreeboxConstants.PROJECTION_WORLD_SIZE
];
var pixelsPerMeter = this.projectedUnitsPerMeter(coords[1]);
//z dimension
var height = coords[2] || 0;
projected.push( height * pixelsPerMeter );
var result = new THREE.Vector3(projected[0], projected[1], projected[2]);
return result;
},
// 投影单位转化
projectedUnitsPerMeter:function (latitude) {
return Math.abs(ThreeboxConstants.WORLD_SIZE * (1 / Math.cos(latitude*Math.PI/180))/ThreeboxConstants.EARTH_CIRCUMFERENCE);
},
// 添加对象
addAtCoordinate:function (Layerid,id,obj,lnglat,options) {
var geoGroup = new THREE.Group();
geoGroup.userData.isGeoGroup = true;
geoGroup.add(obj);
geoGroup.name = id;
var layernum =0;
for (var i=0;i
下面是调用实现
var radius = 40;
var sphereGeom = new THREE.SphereGeometry( radius, 40, 40 ,0,Math.PI,0,Math.PI);
var redMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.5 ,
side: THREE.DoubleSide
});
var GradualGeometry = new THREE.SphereGeometry( radius, 100, 100 ,0,Math.PI,0,Math.PI);
//var GradualGeometry1 = new THREE.BoxBufferGeometry(40,40,40);
var GradualMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE.VertexColors,
transparent: true,
opacity: 0.5 ,
side: THREE.DoubleSide
});
var cube = new THREE.Mesh(GradualGeometry, GradualMaterial);
cube.scale.multiplyScalar(1.07);
var cube1 = new THREE.Mesh(sphereGeom, redMaterial);
var id = "text";
var origin2 = [-122.4341, 37.7354, 0];
var origin3 = [-122.4311,37.7289,100];
AddThreejs.addnewLayer(id);
AddThreejs.addAtCoordinate(id,"1",cube1.clone(), origin);
AddThreejs.addAtCoordinate(id,"2",cube.clone(), origin1);
AddThreejs.addAtCoordinate(id,"3",cube.clone(), origin);
AddThreejs.updateLayer(id,"2",cube1.clone());
AddThreejs.updateLayer(id,"2",GradualMaterial);
var id2 ="2";
AddThreejs.addnewLayer(id2);
AddThreejs.addAtCoordinate(id2,"4",cube1.clone(), origin2);
AddThreejs.addAtCoordinate(id2,"5",cube.clone(), origin3);
添加完对象后还有一个很重要的过程,那就是
添加动画,我这里用的是之前我文章写过的效果,有兴趣的小伙伴可以去看看。下面是添加动画的代码。
animate();
function animate() {
stop = requestAnimationFrame(animate);
AddThreejs.renderer.setSize(window.innerWidth,window.innerHeight);
AddThreejs.addanimate(id,"2");
AddThreejs.addanimate(id,"3");
AddThreejs.renderer.render( AddThreejs.scene, AddThreejs.camera );
};
//donghua
var layernum = 0;
for(var i=0;i
这里需要注意的是,如果你想让动画停止下来的话,需要定义一个对象,值为帧动画requestAnimationFrame的返回值,要关闭时,通过cancelAnimationFrame(stop);可以取消该次动画。在销毁threejs时要注意与创建threejs时完全相反,首先要销毁动画、然后是对象,最后是threejs的初始化创建对象。
最终效果图:(由于太垃圾插入不了视屏(;´д`)ゞ)