用three.js开发三维地图实例

公司要做智慧消防楼层可视化,需要用到web3d,开源的引擎中先研究了cesium三维地球,但cesium做楼层感觉是大材小用,而且体验也不好,最终选用的是功能强大、更适合小型场景的three。

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;
}

原文地址:https://www.cnblogs.com/smedas/p/12440987.html

时间: 2024-10-12 17:41:13

用three.js开发三维地图实例的相关文章

使用three.js开发3d地图初探

three是图形引擎,而web二维三维地图都是基于图形引擎的,所以拿three来开发需求简单的三维地图应用是没什么问题的. 1.坐标转换 实际地理坐标为经度.纬度.高度,而three.js使用的是右手坐标系x.y.z,本来考虑的是将经纬度坐标转换成墨卡托,再去和three的坐标系对应.而实际项目中,经纬度转墨卡托后,墨卡托的值太大,对应到three坐标系中,坐标距离原点太远,用户交互后,会有精度损失,于是先定义一个中间点,然后将墨卡托的结果减去这个中间点的值.(我自己是经度对应z轴,纬度对应x轴

IClient for js开发之地图的加载

进行web开发之前首先需要安装IServer以及iClient for JavaScript的开发包.在这两中都具备的前提下进行第一步,如何调用IServer中发布的服务 调用iServer 中发布的服务(具体的发布服务参见我的其他随笔) 一.用任何一个能编写html和js 的编译器,新建一个html页,将新建的html页保存到一个熟悉的位置,再将iclient for js中lib文件夹和theme文件夹复制到刚才新建的html的文件目录中. 二.进行编写代码: <!DOCTYPE html>

Node.js开发 ---- 留言板实例fs读写文件

项目结构如下: app.js const exp = require('express'), bodyParser = require('body-parser'), fs = require('fs'), multer = require('multer'), app = exp(), multipart = multer(); // 用来创建对象的方法如上面的exp()/multer() // 可以称为工厂方法 app.use(exp.static('static')) app.use(bo

Javascript实战开发:教你使用raphael.js绘制中国地图

最近的数据统计项目中要用到中国地图,也就是在地图上动态的显示某个时间段某个省份地区的统计数据,我们不需要flash,仅仅依靠raphael.js以及SVG图像就可以完成地图的交互操作.在本文中,我给大家分享如何使用js来完成地图交互. 先简单介绍下raphael.js,raphael.js是一个很小的javascript库,它可以在网页中实现绘制各种矢量图.各类图表.以及图像裁剪.旋转.运动动画等等功能.此外raphael.js还跨浏览器兼容,而且还兼容老掉牙的IE6啊.raphael.js的官

网页三维地图技术初探

根据是否需要加载地图服务器中的资源将网页三维地图技术分为两大类: 一.需要服务器配合的 1.cesiumjs    网址:http://www.cesiumjs.org 许可证:Apache 2.0 license 可以在调整经纬度,但是不能调整人眼的视角,也就是地球的轴线不能在显示器的平面中旋转. 优点:可以绘制三维数据,圆柱圆锥.卫星 2.openwebglobe 网址:http://www.openwebglobe.org/ 许可证:Open source licenced under M

GIS(六)——实现js版搜狗地图周边搜索功能

在上一篇文章<GIS(五)--完成js版搜狗地图基本交互搜索功能>中,介绍了搜狗地图的关键字搜索功能,今天就实现以下另一个重要功能吧--那就是周边搜索功能. 按照惯例,还是把官网上的示例代码给大家贴出来.飞机票在此.周边搜索的功能,跟关键字搜索其实是一样的,也是主要用到的了SearchRequest这个类,点击这里查看api文档.SearchRequest 对象规范: 属性 类型 说明 map Map 进行搜索的地图实例 renderer SearchRenderer 将结果进行渲染的对象.也

arcgis api 4.x for js之基础地图篇

arcgis api3.x for js转向arcgis api4.x,我也是最近的3-4个月时间的事情,刚好公司有个webgis项目需要展示三维场景,项目选择arcgis api4.x.我纯碎记录一下自己学习arcgis api4.x过程中的一些心得体会,自己个人的一些理解,不对之处,还请各位同仁见谅以及多多指教. 一.谈谈本篇自己认为比较重要的几个专业术语名称的理解: 1.View View即视图,提供了查看和与地图组件交互的方法.地图仅仅是一个容器,存储了包含在基础层和操作层中的地理信息,

使用 node.js 开发前端打包程序 ---转载

我们在做前端开发的时候经常会在部署上线的时候做程序的打包和合并,我们接下来就会对如何使用 node.js 开发前端打包程序做非常深入的讲解,希望能够帮到有需要的同学. 我们现在做前端开发更多的是多人共同协作开发,每个人负责不同的模块,便于开发和调试.这样就导致我们最后部署上线的时候需要把所有人开发的模块进行合并,生成单个或多个文件上线.如果手动合并的话肯定是费时又费力,而且非常容易出错,所以我们一般都是通过一些工具来实现自动合并的功能. 打包程序的原理非常简单,入口文件->寻找依赖关系->替换

JS调用百度地图拼接成路径,C#保存地图图片到本地

昨日公司要求做一个静态百度地图图片生成到本地的功能,以前没做过,这次小小总结一下 百度地图API自己看(http://developer.baidu.com/map/index.php?title=static) 当然首先得调用百度地图API,无非就是前台后台,,我是用JS写的,(http://www.cnblogs.com/kulong995/p/3368048.html)这个人写的不错,是用c#写的调用百度地图,写的很不错,可以看看 百度地图是由一堆乱七八糟的数据拼接出来的路径,然而保存图片