使用three.js开发3d地图初探
three是图形引擎,而web二维三维地图都是基于图形引擎的,所以拿three来开发需求简单的三维地图应用是没什么问题的。
1.坐标转换
实际地理坐标为经度、纬度、高度,而three.js使用的是右手坐标系x、y、z,本来考虑的是将经纬度坐标转换成墨卡托,再去和three的坐标系对应。而实际项目中,经纬度转墨卡托后,墨卡托的值太大,对应到three坐标系中,坐标距离原点太远,用户交互后,会有精度损失,于是先定义一个中间点,然后将墨卡托的结果减去这个中间点的值。(我自己是经度对应z轴,纬度对应x轴,高度对应y轴)
function lonlatToMercator(lon,lat,height){
var z = height ? height:0;
var x = (lon / 180.0) * 20037508.3427892;
var y = (Math.PI / 180.0) * lat;
var tmp = Math.PI / 4.0 + y / 2.0;
y = 20037508.3427892 * Math.log(Math.tan(tmp)) / Math.PI;
return {x: x,y: y,z: z};
}
var center = lonlatToMercator(lonVal,latVal,heightVal);
function lonlatToThree(lon,lat,height){
var z = height? height:0;
var x = (lon / 180.0) * 20037508.3427892;
var y = (Math.PI / 180.0) * lat;
var tmp = Math.PI / 4.0 + y / 2.0;
y = 20037508.3427892 * Math.log(Math.tan(tmp)) / Math.PI;
var result = {
x: x - center.x,
y: y - center.y,
z: z -center.z
};
return result;
}
2.加载模型
three.js支持多种模型加载,我是用草图大师建的模型,于是直接转成collada模型,然后使用three的collada模型加载器加载模型。因为要和three.js对应,而模型默认位于x-z轴上,所以要进行模型翻转等操作。
3.创建标注
three中,创建始终朝向相机的POI标注可以使用Sprite类,也可以使用canvas创建图标+文字类型的图形作为Sprite的纹理。sprite默认是有一个固定的3d长度,相机距离sprite越近,sprite在屏幕上越大,反之越小,过大或者过小都会导致sprite的canvas失真模糊,解决方案是计算出该点的屏幕像素与3d坐标长度的比值,然后将sprite缩放到一个合适的3d长度。
var position = sprite.position;
var canvas = sprite.material.map.image;
if(canvas){
var poiRect = {w:canvas.width,h:canvas.height};
var scale = getPoiScale(position,poiRect);
sprite.scale.set(scale[0],scale[1],1.0);
}
function getPoiScale(position,poiRect){
if(!position) return;
var distance = camera.position.distanceTo(position);
var top = Math.tan(camera.fov / 2 * Math.PI / 180)*distance; //camera.fov 相机的拍摄角度
var meterPerPixel = 2*top/container.clientHeight;
var scaleX = poiRect.w * meterPerPixel;
var scaleY = poiRect.h * meterPerPixel;
return [scaleX,scaleY,1.0];
}
4.标注碰撞
创建标注之后,放缩时难免会出现标注相互遮盖的情况,这样既影响美观也会遮盖住地图信息,这里需要检测标注间的遮盖,显示和不显示一些标注。
这里主要是将标注点3d坐标转成屏幕坐标,再根据sprite中canvas的长度和高度,就可以知道sprite在屏幕的矩形范围。接下来就是计算各个标注点sprite的矩形相交了。
var sprite1 = {x:x1,y:y1,w:w1,h:h1}; //sprite1左下角x,y,宽度、高度
var sprite2 = {x:x2,y:y2,w:w2,h:h2}; //sprite2左下角x,y,宽度、高度
//检测两个标注sprite是否碰撞
function isPOIRect(sprite1,sprite2){
var x1 = sprite1.x,y1=sprite1.y,w1=sprite1.w,h1=sprite1.h;
var x2 = sprite2.x,y2=sprite2.y,w1=sprite2.w,h1=sprite2.h;
if (x1 >= x2 && x1 >= x2 + w2) {
return false;
} else if (x1 <= x2 && x1 + w1 <= x2) {
return false;
} else if (y1 >= y2 && y1 >= y2 + h2) {
return false;
} else if (y1 <= y2 && y1 + h1 <= y2) {
return false;
}else{
return true;
}
}
5.加载设备
创建设备,我同样使用的是Sprite类,跟创建标注类似,放缩之后,sprite在屏幕上的大小保持不变。
6.设备点击
raycaster类用于在3d中被鼠标选中的物体,这同样可以选中sprite对象,于是用此方法模拟设备的点击。其中deviceGroup是保存所有设备sprite的object3d对象。
function onDocumentMouseDown(e) {
e.preventDefault();
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
//新建一个三维单位向量 假设z方向就是0.5
//根据照相机,把这个向量转换到视点坐标系
var vector = new THREE.Vector3(mouse.x, mouse.y,0.5).unproject(camera);
//在视点坐标系中形成射线,射线的起点向量是照相机, 射线的方向向量是照相机到点击的点,这个向量应该归一标准化。
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
//射线和模型求交,选中一系列直线
var intersects = raycaster.intersectObjects([deviceGroup],true);
if (intersects.length > 0) {
var intersected = intersects[0].object;
if(intersected instanceof THREE.Sprite){
//点击到设备图标
}
}
}
7.弹出框
设备点击之后,一般都会以弹出框形式展示设备的具体信息,这里需要先定义弹出框的样式,然后将弹出点设备的三维坐标转换成屏幕坐标,设置一定的偏移量,再将弹出框放到偏移后的屏幕位置上。然后每次更改相机,重新计算弹出框的位置。
//three世界坐标转为屏幕坐标
function threeToScreen(position,camera){
var worldVector = new THREE.Vector3(
position.x,
position.y,
position.z
);
var standardVector = worldVector.project(camera);//世界坐标转标准设备坐标
var a = window.innerWidth / 2;
var b = window.innerHeight / 2;
var x = Math.round(standardVector.x * a + a);//标准设备坐标转屏幕坐标
var y = Math.round(-standardVector.y * b + b);//标准设备坐标转屏幕坐标
return {
x: x,
y: y
};
}
8.设备动画
简单设备动画可以通过更改设备的材质、大小、位置来实现,比如通过定时更改设备的材质来实现设备图标的闪烁。
项目中要模拟火情,因此花了些时间网上参考并用粒子系统做了个火焰动画,这里先用一个循环通过THREE.Vector3对象创建构成火焰的全部的点,放到THREE.Geometry对象的vertices中;再使用canvas创建火焰的纹理图形,传给THREE.PointsMaterial对象(并设置材质透明transparent:true和加法混合THREE.AddictiveBlending),最后以前面的THREE.Geometry和THREE.PointsMaterial创建THREE.Points对象,完成该火焰粒子系统的初始化。
每个粒子都有单独的坐标,最后用一定的规律驱动粒子的移动达到动画的效果。
9.鼠标绘制
在3d中,鼠标的位置对应到三维坐标中是一条射线,因此需要添加绘制平面,点击时获取鼠标和绘制平面的交点,作为绘制点。绘制时监听鼠标的单击和移动事件。
绘制线时,鼠标点击和移动时,直接更改线的geometry中的vertices;绘制面时,不仅仅要更改vertices还要计算所有顶点组合的三角面(我使用的是Earcut.js),作为geometry的faces,最后创建一个以这个geometry为几何形状的多边形mesh。
//positions 三维坐标数组[[x,y,z],[x,y,z],...]
function createPolygon(positions){
var shapePositons = [];
for(var i=0;i<positions.length;i++){
var position = positions[i];
shapePositons.push(new THREE.Vector3(position[0],position[1],position[2]));
}
var data = [];
for(var i=0;i<positions.length;i++){
var position = positions[i];
data.push(position[0],position[1]);
}
var faces = [];
var triangles = Earcut.triangulate(data);
if(triangles && triangles.length != 0){
for(var i=0;i<triangles.length;i++){
var length = triangles.length;
if(i%3==0 && i < length-2){
faces.push(new THREE.Face3(triangles[i],triangles[i+1],triangles[i+2]));
}
}
}
var geometry = new THREE.BufferGeometry();
geometry.vertices = shapePositons;
geometry.faces = faces;
var mesh = new THREE.Mesh(geometry,material);
return mesh;
}
使用three.js开发3d地图初探的更多相关文章
- Threejs 开发3D地图实践总结【转】
Threejs 开发3D地图实践总结 前段时间连续上了一个月班,加班加点完成了一个3D攻坚项目.也算是由传统web转型到webgl图形学开发中,坑不少,做了一下总结分享. 1.法向量问题 法线是垂 ...
- Threejs 开发3D地图实践总结
前段时间连续上了一个月班,加班加点完成了一个3D攻坚项目.也算是由传统web转型到webgl图形学开发中,坑不少,做了一下总结分享. 1.法向量问题 法线是垂直于我们想要照亮的物体表面的向量.法线代表 ...
- Three.js实现3D地图实例分享
本文主要给大家介绍了关于利用Three.js开发实现3D地图的实践过程,文中通过示例代码介绍的非常详细,对大家学习或者使用three.js具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习 ...
- three.js 绘制3d地图
通过地图数据配合three可以做出非常酷炫的地图,在大数据展示中十分常见. 这篇郭先生就来说说使用three.js几何体制作3D地图.在线案例点击原文地址. 地图的数据是各个地图块的点数组,通过THR ...
- 用three.js开发三维地图实例
公司要做智慧消防楼层可视化,需要用到web3d,开源的引擎中先研究了cesium三维地球,但cesium做楼层感觉是大材小用,而且体验也不好,最终选用的是功能强大.更适合小型场景的three. thr ...
- IClient for js开发之地图的加载
进行web开发之前首先需要安装IServer以及iClient for JavaScript的开发包.在这两中都具备的前提下进行第一步,如何调用IServer中发布的服务 调用iServer 中发布的 ...
- Windows 10 新特性 -- Bing Maps 3D地图开发入门(一)
本文主要内容是讲述如何创建基于 Windows Universal App 的Windows 10 3D地图应用,涉及的Windows 10新特性包括 Bing Maps 控件.Compiled da ...
- html5游戏开发--"动静"结合用地图块拼成大地图 & 初探lufyl
一.前言 本次教程将向大家讲解如何用html5将小地图块拼成大地图,以及如何用现有的高级html5游戏开发库件lufylegend.js开发游戏. 首先让我们来了解了解如何用html5实现动画 ...
- html5游戏开发--"动静"结合(二)-用地图块拼成大地图 & 初探lufylegend
一.前言 本次教程将向大家讲解如何用HTML5将小地图块拼成大地图,以及如何用现有的高级html5游戏开发库件lufylegend.js开发游戏. 首先让我们来了解了解如何用html5实现动画,毕竟“ ...
随机推荐
- Visual Studio 2015 编译错误 File 的值+乱码的解决方法
======================================== VS2015调试项目时,会报莫名奇妙的错误,如下图所示: 程序编译,提示有错误:Visual Studio 2015 ...
- 《DSP using MATLAB》Problem 3.8
2018年元旦,他乡加班中,外面尽是些放炮的,别人的繁华与我无关. 代码: %% ----------------------------------------------------------- ...
- 作为一名IT从业者,你在工作和学习中,遇到哪些问题
版权声明:襄阳雷哥的版权声明 https://blog.csdn.net/FansUnion/article/details/28448975 大家都是IT从业者,遇到的问题多少与类似. 假设能把这些 ...
- linux和mac使用virtualenv使用和安装
virtualenv是python的三大神器之一,用于创建独立的python虚拟环境,多个python版本相互独立,互不影响,可以在一台电 脑上同时安装多个版本的python,而且不影响本机pytho ...
- 箭头函数中的 this
JS 每一个 function 都有自己独立的运行上下文,但箭头函数不属于普通的 function,所以没有独立的上下文. 所以在箭头函数里写的 this 其实是包含该箭头函数最近的一个 functi ...
- Centos 6.5 yum 安装Apache软件
首先在系统上面查询一下是否已经安装了apache 软件[Apache软件在linux系统里的名字是httpd] rpm -qa httpd 如果有返回的信息,则会显示已经安装的软件.如果没 ...
- 【Reporting Services 报表开发】— 怎么根据当前表单的guid作为参数查询相关数据?
select AId from FilteredA as CRMAF_FilteredA 用这个 作为一个DataSet1 , 然后添加在报表里面添加一个参数 @AId,设置的默认的查询为前面Data ...
- 10个CSS+HOVER 的创意按钮
CSS hover 样式很简单,但是想创造出有意思.实用.有创意性的特效是很考验设计师的创意能力,所以设计达人每隔一段时间都会分享一些与鼠标点击.悬停的相关特效,以便大家获得更好的创造灵感. 今天我们 ...
- WPF Demo8
namespace Demo10 { public class Student { private string name; public string Name { get { return nam ...
- C/C++基础----类
IO类属于不能被拷贝的类型,因此只能通过引用来传递.同时读取和写入操作都会改变流的内容,所以接收的是普通引用. 类内的友元声明仅仅指定了访问的权限,需要在友元声明之外再专门对函数进行一次声明. 可变数 ...