├── README.md ├── Version.txt ├── WEB-INF └── web.xml ├── deprecated ├── FragmentShader_min.txt ├── Globe.js ├── VertexShader_min.txt └── index.html ├── images ├── WebGL.png └── iSpring.png ├── index.html ├── js └── world │ ├── ArcGISTiledLayer.js │ ├── AutonaviTiledLayer.js │ ├── BingTiledLayer.js │ ├── BlendTiledLayer.js │ ├── Elevation.js │ ├── Enum.js │ ├── Event.js │ ├── Globe.js │ ├── GoogleTiledLayer.js │ ├── Image.js │ ├── Kernel.js │ ├── Line.js │ ├── Math.js │ ├── Matrix.js │ ├── NokiaTiledLayer.js │ ├── Object3D.js │ ├── Object3DComponents.js │ ├── OsmTiledLayer.js │ ├── PerspectiveCamera.js │ ├── Plan.js │ ├── Ray.js │ ├── Scene.js │ ├── ShaderContent.js │ ├── SosoTiledLayer.js │ ├── SubTiledLayer.js │ ├── TextureMaterial.js │ ├── TiandituTiledLayer.js │ ├── Tile.js │ ├── TileGrid.js │ ├── TileMaterial.js │ ├── TiledLayer.js │ ├── Utils.js │ ├── Vector.js │ ├── Vertice.js │ └── WebGLRenderer.js ├── proxy.jsp ├── require.js ├── screenshot.png └── ts └── world ├── Enum.ts ├── Kernel.ts ├── Math.ts └── Utils.ts /README.md: -------------------------------------------------------------------------------- 1 | # WebGlobe 2 | HTML5基于原生WebGL实现的三维地球,没有使用第三方框架,无需插件,所有支持WebGL的浏览器均可使用。 3 | 4 | **如果觉得不错,欢迎Star和Fork!** 5 | 6 | Demo: https://ispring.github.io/WebGlobe/index.html 7 | 8 | ![这里写图片描述](https://github.com/iSpring/WebGlobe/blob/master/screenshot.png) 9 | -------------------------------------------------------------------------------- /Version.txt: -------------------------------------------------------------------------------- 1 | 0.4.4 优化了判断切片是否可见的算法,使用经纬度判断法(isTileVisible2)取代了老式的计算方法(isTileVisible) 2 | 3 | 0.4.5 继续优化isTileVisible算法,许多参数从函数外面传递进来,比如geoExtent和projViewMatrix,循环传递使用,减少了生成这两个变量所调用函数的次数;引入了World.Globe,将一些处理逻辑放到了Globe中,比如讲renderer中的updateGlobe方法迁移到Globe中的refresh方法,尽量只对二次开发人员暴露World.Globe的接口,更合理一些,从而将版本从0.4.4升级到0.4.5 4 | 5 | 0.4.6 不再通过ajax获取vertexShader和fragmentShader的文本内容,而是将其以字符串的形式直接放入到World.js文件中(World.ShaderContent),从而减少了对于其他GLSL文件的依赖,并因此将World.js的版本升级到0.4.6 6 | 7 | 0.4.7 新增了World.TiledLayer类型,取消掉了World.NokiaTile等类型,图层都继承自World.TiledLayer,并重写其中的getImageUrl方法,所有的切片都放到World.TiledLayer中进行管理,结构化明确,更加面向对象,因此将版本升级到0.4.7 8 | 9 | 0.5.0 从0.4.7直接升级到0.5.0,在TiledLayer的基础上又增加了SubTiledLayer类,并在此基础上优化了诸多算法,包括通过添加isvisible判断是否渲染,完善了camera的getCurrentGeoExtent算法,结构更合理,访问速度更快,故升级到0.5.0 10 | 11 | 0.5.1 优化了各种TiledLayer的getImageUrl的算法 12 | 13 | 0.5.2 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); 14 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); 15 | 通过执行以上命令消除了两个切片之间的白线问题 16 | 17 | 0.5.3 从0.5.2升级到0.5.3:globe中增加了calculateCameraMoveInfo方法,在大比例尺下移动视图也比较正常 18 | 19 | 0.5.4 升级到0.5.4:在World.Object3D的destroy方法中释放了显卡中的各种buffer和texture资源 20 | 21 | 0.5.5 在render方法中计算了viewMatrix并赋值给camera作为其中的一个属性,这样就只调用一次getViweMatrix方法,不用在draw(camera)中再多次计算 22 | 23 | 0.5.6 在创建level为1的subTiledLayer的时候就全部创建其下的四个切片,并且从不删除该subTiledLayer;将自动更新时间设置为750毫秒 24 | 25 | 0.5.7 添加了World.BingTiledLayer,支持微软Bing地图渲染(有三种风格:a、r、h) 26 | 27 | 0.5.8 添加了World.SosoTiledLayer,支持soso地图 28 | 29 | 0.5.9 添加了World.TiandituTiledLayer,支持天地图 30 | 31 | 0.6.0 通过添加代理页proxy.jsp可以加载ArcGIS的切片地图,包括ArcGISOnline以及捷泰地图,或其他ArcGIS Server发布的地图服务 32 | 33 | 0.6.1 通过代理方式支持高德地图 34 | 35 | 0.6.2 用bind方式代替闭包实现World.TextureMaterial.prototype.setImageUrl中handleLoadedTexture操作texture 36 | 37 | 0.6.3 在Matrix和Object3D中加入对worldScale和localScale的支持 38 | 39 | 0.6.4 在Matrix和Object3D中加入对localRotateByVector的支持 40 | 41 | 0.6.5 修复了在层级比较大的情况下切片不能完全正常匹配的问题,解决办法是取消掉了对切片范围的一像素扩容 42 | 43 | 0.6.6 将World.Scene继承自World.Object3DComponents,减少冗余代码,结构也更合理,也因此调整了World.js中各代码块的位置 44 | 45 | 0.6.7 在拖动鼠标时,鼠标拖动点与经纬度进行了绑定,实现定点拖动 46 | 47 | 0.6.8 将CanvasEventHandler.js中的事件代码移植到World.Event中,以后在html页面中只需要引入World.js一个js文件即可 48 | 49 | 0.6.9 修改了camera中的isLonVisible方法,将其精简完善,从而解决了在伦敦中央经线附近在14级时一片漆黑的问题 50 | 51 | 0.7.0 修改了无法删除canvas的onMouseMove事件的问题 52 | 53 | 0.7.1 修改了将视图缩放到中国区域范围的时候,camera.getGeoExtent()获取的经纬度不准确的问题,原因是在计算由于地球自身视线遮挡的经度范围时,只是简单的在中心经度上加减偏移量,对于如果中心点的纬度为0时是正确的,只要中心点纬度不为0,这种简单的加减经度偏移量是不准确的 54 | 55 | 0.7.2 完善了各种坐标系之间坐标的换算 56 | 57 | 0.7.3 基本完成camera.getExtentInfo的编写,准备用其替换camera.getGeoExtent方法,并添加了camera.isGeoVisibleInCanvas方法 58 | 59 | 0.7.4 继续晚上camera.getExtentInfo方法,在沿着经线上下偏移纬度时不准确,比如65+40=105>90,导致其超出[-90,90]的范围,现在会确保其范围是正确的,但是有可能把范围算大了 60 | 61 | 0.7.5 完成camera.getExtentInfo方法的重写,并对老旧代码删除,用新代码替换老代码 62 | 63 | 0.7.6 以前为了优化效率,在很多地方设置了SubTiledLayer的isvisible和Tile的isvisible都为false,但是体验效果却不好,经常会拿level1来作为保底图层;现在只要camera变化了就将所有SubTiledLayer和Tile的isvisible都设置为true,体验效果好,只有在camera位置没有发生变化,且level最大的那级图层的所有切片都已加载的情况下(即SubTiledLayer.checkIfLoaded为true)时才会将无需显示的那些SubTiledLayer的isvisible属性置为false 64 | 65 | 0.7.7 将MAX_LEVEL、CURRENT_LEVEL、BELOW_LEVEL、OLD_POSITION属性从World中迁移如World.Globe中,更合理 66 | 67 | 0.7.8 完善了onMouseMove事件代码,提供与Google New Map一样的体验 68 | 69 | 0.7.9 修改完善了getPickDirectionByNDC方法,ndcZ传入0.1而非0.5,0.5在某些情况下会导致计算的w为0 70 | 71 | 0.8.0 将ndcZ最终改为0.499,没发现问题;并且在camera.isTileVisible方法内通过判断切片斜对角线的长度是否均小于0.1来决定不可见或可见,这对于倾斜视角下很有用,会剔除掉那些面积很小的切片,直接用面积比较不如用斜对角线长度比较实用,并因此在任意时刻都渲染全部的切片,去掉了checkIfLoaded的优化代码 72 | 73 | 0.8.1 打算重写算法,添加了两个重要的方法camera.getVisibleTilesByLevel和getTileVisibleInfo,直接从第level级获取可见的切片,无需从第0级依次遍历 74 | 75 | 0.8.2 重写了算法,不再根据经纬度依次从第1级开始判断,直接通过camera.getVisibleTilesByLevel获取第level级别的可见切片,即使缩放到20级,内存也在170M左右,而且很流畅,删除了以前的老代码,实现重大升级 76 | 77 | 0.8.3 加入World.Elevation代码,用于请求高程数据,开始改造Object3D和Tile的相关方法,使其能根据传入的vertice等信息动态更改buffer 78 | 79 | 0.8.4 实现最最基础的三维地形图 80 | 81 | 0.8.5 上一版本的World.Elevation.getElevationByCache有问题,现在已修改完善,正确计算切片高程的起点索引值 82 | 83 | 0.8.8 重写了Tile.handleTile方法,将老的方法命名为handTile2,但没有删除,新的handleTile方法从左上角至右下角遍历,并且多个三角形公用同一个顶点 -------------------------------------------------------------------------------- /WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | index.html 9 | index.jsp 10 | 11 | -------------------------------------------------------------------------------- /deprecated/FragmentShader_min.txt: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform bool uUseTexture; 6 | uniform float uShininess; 7 | uniform vec3 uLightDirection; 8 | 9 | uniform vec4 uLightAmbient; 10 | uniform vec4 uLightDiffuse; 11 | uniform vec4 uLightSpecular; 12 | 13 | varying vec2 vTextureCoord; 14 | uniform sampler2D uSampler; 15 | 16 | void main() 17 | { 18 | gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); 19 | } -------------------------------------------------------------------------------- /deprecated/VertexShader_min.txt: -------------------------------------------------------------------------------- 1 | attribute vec3 aVertexPosition; 2 | attribute vec2 aTextureCoord; 3 | varying vec2 vTextureCoord; 4 | 5 | uniform mat4 uMVMatrix; 6 | uniform mat4 uPMatrix; 7 | 8 | void main() 9 | { 10 | gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition,1.0); 11 | vTextureCoord = aTextureCoord; 12 | } -------------------------------------------------------------------------------- /deprecated/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WebGlobe 5 | 6 | 7 | 13 | 14 | 67 | 68 | 69 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /images/WebGL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wandergis/WebGlobe/06bef518d3b0c6f702922e8a22c2e2b90ae102bb/images/WebGL.png -------------------------------------------------------------------------------- /images/iSpring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wandergis/WebGlobe/06bef518d3b0c6f702922e8a22c2e2b90ae102bb/images/iSpring.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WebGlobe 5 | 6 | 7 | 13 | 14 | 15 | 24 | 25 | 80 | 81 | 82 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /js/world/ArcGISTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/TiledLayer"], function(Kernel, TiledLayer) { 2 | //使用代理 3 | var ArcGISTiledLayer = function(args) { 4 | TiledLayer.apply(this, arguments); 5 | this.service = ""; 6 | if (args) { 7 | if (args.url) { 8 | this.service = args.url; 9 | } 10 | } 11 | }; 12 | ArcGISTiledLayer.prototype = new TiledLayer(); 13 | ArcGISTiledLayer.prototype.constructor = ArcGISTiledLayer; 14 | ArcGISTiledLayer.prototype.getImageUrl = function(level, row, column) { 15 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 16 | var url = Kernel.proxy + "?" + this.service + "/tile/" + level + "/" + row + "/" + column; 17 | return url; 18 | }; 19 | return ArcGISTiledLayer; 20 | }); -------------------------------------------------------------------------------- /js/world/AutonaviTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/TiledLayer"], function(Kernel, TiledLayer) { 2 | //使用代理 3 | var AutonaviTiledLayer = function(args) { 4 | TiledLayer.apply(this, arguments); 5 | }; 6 | AutonaviTiledLayer.prototype = new TiledLayer(); 7 | AutonaviTiledLayer.prototype.constructor = AutonaviTiledLayer; 8 | AutonaviTiledLayer.prototype.getImageUrl = function(level, row, column) { 9 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 10 | var sum = level + row + column; 11 | var serverIdx = 1 + sum % 4; //1、2、3、4 12 | var url = Kernel.proxy + "?//webrd0" + serverIdx + ".is.autonavi.com/appmaptile?x=" + column + "&y=" + row + "&z=" + level + "&lang=zh_cn&size=1&scale=1&style=8"; 13 | return url; 14 | }; 15 | return AutonaviTiledLayer; 16 | }); -------------------------------------------------------------------------------- /js/world/BingTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/Math", "world/TiledLayer"], function(MathUtils, TiledLayer) { 2 | //Bing地图 3 | var BingTiledLayer = function(args) { 4 | TiledLayer.apply(this, arguments); 5 | }; 6 | BingTiledLayer.prototype = new TiledLayer(); 7 | BingTiledLayer.prototype.constructor = BingTiledLayer; 8 | BingTiledLayer.prototype.getImageUrl = function(level, row, column) { 9 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 10 | var url = ""; 11 | var tileX = column; 12 | var tileY = row; 13 | var strTileX2 = MathUtils.numerationSystemFrom10(2, tileX); 14 | var strTileY2 = MathUtils.numerationSystemFrom10(2, tileY); 15 | var delta = strTileX2.length - strTileY2.length; 16 | var i; 17 | if (delta > 0) { 18 | for (i = 0; i < delta; i++) { 19 | strTileY2 = '0' + strTileY2; 20 | } 21 | } else if (delta < 0) { 22 | delta = -delta; 23 | for (i = 0; i < delta; i++) { 24 | strTileX2 = '0' + strTileX2; 25 | } 26 | } 27 | var strMerge2 = ""; 28 | for (i = 0; i < strTileY2.length; i++) { 29 | var charY = strTileY2[i]; 30 | var charX = strTileX2[i]; 31 | strMerge2 += charY + charX; 32 | } 33 | var strMerge4 = MathUtils.numerationSystemChange(2, 4, strMerge2); 34 | if (strMerge4.length < level) { 35 | delta = level - strMerge4.length; 36 | for (i = 0; i < delta; i++) { 37 | strMerge4 = '0' + strMerge4; 38 | } 39 | } 40 | var sum = level + row + column; 41 | var serverIdx = sum % 8; //0,1,2,3,4,5,6,7 42 | //var styles = ['a','r','h'] 43 | url = "//ecn.t" + serverIdx + ".tiles.virtualearth.net/tiles/h" + strMerge4 + ".jpeg?g=1239&mkt=en-us"; 44 | return url; 45 | }; 46 | 47 | return BingTiledLayer; 48 | }); -------------------------------------------------------------------------------- /js/world/BlendTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/TiledLayer", "world/NokiaTiledLayer", "world/GoogleTiledLayer", "world/OsmTiledLayer"], function(TiledLayer) { 2 | var BlendTiledLayer = function(args) { 3 | TiledLayer.apply(this, arguments); 4 | }; 5 | BlendTiledLayer.prototype = new TiledLayer(); 6 | BlendTiledLayer.prototype.constructor = BlendTiledLayer; 7 | BlendTiledLayer.prototype.getImageUrl = function(level, row, column) { 8 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 9 | var array = [NokiaTiledLayer, GoogleTiledLayer, OsmTiledLayer]; 10 | var sum = level + row + column; 11 | var idx = sum % 3; 12 | var url = array[idx].prototype.getImageUrl.apply(this, arguments); 13 | return url; 14 | }; 15 | return BlendTiledLayer; 16 | }); -------------------------------------------------------------------------------- /js/world/Elevation.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Utils", "world/Math"], function(Kernel, Utils, MathUtils) { 2 | var Elevation = { 3 | //sampleserver4.arcgisonline.com 4 | //23.21.85.73 5 | elevationUrl: "//sampleserver4.arcgisonline.com/ArcGIS/rest/services/Elevation/ESRI_Elevation_World/MapServer/exts/ElevationsSOE/ElevationLayers/1/GetElevationData", 6 | elevations: {}, //缓存的高程数据 7 | factor: 1 //高程缩放因子 8 | }; 9 | //根据level获取包含level高程信息的ancestorElevationLevel 10 | Elevation.getAncestorElevationLevel = function(level) { 11 | if (!Utils.isNonNegativeInteger(level)) { 12 | throw "invalid level"; 13 | } 14 | var a = Math.floor((level - 1 - Kernel.ELEVATION_LEVEL) / 3); 15 | var ancestor = Kernel.ELEVATION_LEVEL + 3 * a; 16 | return ancestor; 17 | }; 18 | 19 | /** 20 | * 根据传入的extent以及行列数请求高程数据,返回(segment+1) * (segment+1)个数据,且乘积不能超过10000 21 | * 也就是说如果传递的是一个正方形的extent,那么segment最大取99,此处设置的segment是80 22 | */ 23 | Elevation.requestElevationsByTileGrid = function(level, row, column) { 24 | if (!Utils.isNonNegativeInteger(level)) { 25 | throw "invalid level"; 26 | } 27 | if (!Utils.isNonNegativeInteger(row)) { 28 | throw "invalid row"; 29 | } 30 | if (!Utils.isNonNegativeInteger(column)) { 31 | throw "invalid column"; 32 | } 33 | var segment = 80; 34 | var name = level + "_" + row + "_" + column; 35 | //只要elevations中有属性name,那么就表示该高程已经请求过或正在请求,这样就不要重新请求了 36 | //只有在完全没请求过的情况下去请求高程数据 37 | if (this.elevations.hasOwnProperty(name)) { 38 | return; 39 | } 40 | this.elevations[name] = null; 41 | var Eproj = MathUtils.getTileWebMercatorEnvelopeByGrid(level, row, column); 42 | var minX = Eproj.minX; 43 | var minY = Eproj.minY; 44 | var maxX = Eproj.maxX; 45 | var maxY = Eproj.maxY; 46 | var gridWidth = (maxX - minX) / segment; 47 | var gridHeight = (maxY - minY) / segment; 48 | var a = gridWidth / 2; 49 | var b = gridHeight / 2; 50 | var extent = { 51 | xmin: minX - a, 52 | ymin: minY - b, 53 | xmax: maxX + a, 54 | ymax: maxY + b, 55 | spatialReference: { 56 | wkid: 102100 57 | } 58 | }; 59 | var strExtent = encodeURIComponent(JSON.stringify(extent)); 60 | var rows = segment + 1; 61 | var columns = segment + 1; 62 | var f = "pjson"; 63 | var args = "Extent=" + strExtent + "&Rows=" + rows + "&Columns=" + columns + "&f=" + f; 64 | var xhr = new XMLHttpRequest(); 65 | 66 | function callback() { 67 | if (xhr.readyState == 4 && xhr.status == 200) { 68 | try { 69 | var result = JSON.parse(xhr.responseText); 70 | if (this.factor == 1) { 71 | this.elevations[name] = result.data; 72 | } else { 73 | this.elevations[name] = Utils.map(function(item) { 74 | return item * this.factor; 75 | }.bind(this)); 76 | } 77 | } catch (e) { 78 | console.error("requestElevationsByTileGrid_callback error", e); 79 | } 80 | } 81 | } 82 | xhr.onreadystatechange = callback.bind(this); 83 | xhr.open("GET", "proxy.jsp?" + this.elevationUrl + "?" + args, true); 84 | xhr.send(); 85 | }; 86 | 87 | 88 | //无论怎样都尽量返回高程值,如果存在精确的高程,就获取精确高程;如果精确高程不存在,就返回上一个高程级别的估算高程 89 | //有可能 90 | Elevation.getElevation = function(level, row, column) { 91 | if (!Utils.isNonNegativeInteger(level)) { 92 | throw "invalid level"; 93 | } 94 | if (!Utils.isNonNegativeInteger(row)) { 95 | throw "invalid row"; 96 | } 97 | if (!Utils.isNonNegativeInteger(column)) { 98 | throw "invalid column"; 99 | } 100 | var result = null; 101 | var exactResult = this.getExactElevation(level, row, column); 102 | if (exactResult) { 103 | //获取到准确高程 104 | result = exactResult; 105 | } else { 106 | //获取插值高程 107 | result = this.getLinearElevation(level, row, column); 108 | } 109 | return result; 110 | }; 111 | 112 | //把>=8级的任意一个切片的tileGrid传进去,返回其高程值,该高程值是经过过滤了的,就是从大切片数据中抽吸出了其自身的高程信息 113 | //获取准确高程 114 | Elevation.getExactElevation = function(level, row, column) { 115 | if (!Utils.isNonNegativeInteger(level)) { 116 | throw "invalid level"; 117 | } 118 | if (!Utils.isNonNegativeInteger(row)) { 119 | throw "invalid row"; 120 | } 121 | if (!Utils.isNonNegativeInteger(column)) { 122 | throw "invalid column"; 123 | } 124 | var result = null; 125 | var elevationLevel = this.getAncestorElevationLevel(level); 126 | var elevationTileGrid = MathUtils.getTileGridAncestor(elevationLevel, level, row, column); 127 | var elevationTileName = elevationTileGrid.level + "_" + elevationTileGrid.row + "_" + elevationTileGrid.column; 128 | var ancestorElevations = this.elevations[elevationTileName]; 129 | if (ancestorElevations instanceof Array && ancestorElevations.length > 0) { 130 | if (level > Kernel.ELEVATION_LEVEL) { 131 | //ltTileGridLevel表示level级别下位于Tile7左上角的TileGrid 132 | var ltTileGridLevel = { 133 | level: elevationTileGrid.level, 134 | row: elevationTileGrid.row, 135 | column: elevationTileGrid.column 136 | }; //与level在同级别下但是在Tile7左上角的那个TileGrid 137 | while (ltTileGridLevel.level != level) { 138 | ltTileGridLevel = MathUtils.getTileGridByParent(ltTileGridLevel.level, ltTileGridLevel.row, ltTileGridLevel.column, MathUtils.LEFT_TOP); 139 | } 140 | if (ltTileGridLevel.level == level) { 141 | //bigRow表示在level等级下当前grid距离左上角的grid的行数 142 | var bigRow = row - ltTileGridLevel.row; 143 | //bigColumn表示在level等级下当前grid距离左上角的grid的列数 144 | var bigColumn = column - ltTileGridLevel.column; 145 | var a = 81; //T7包含(80+1)*(80+1)个高程数据 146 | var deltaLevel = (elevationLevel + 3) - level; //当前level与T10相差的等级 147 | var deltaCount = Math.pow(2, deltaLevel); //一个当前tile能包含deltaCount*deltaCount个第10级的tile 148 | //startSmallIndex表示该tile的左上角点在81*81的点阵中的索引号 149 | //bigRow*deltaCount表示当前切片距离T7最上面的切片的行包含了多少T10行,再乘以10表示跨过的高程点阵行数 150 | //bigColumn*deltaCount表示当前切片距离T7最左边的切片的列包含了多少T10列,再乘以10表示跨国的高程点阵列数 151 | var startSmallIndex = (bigRow * deltaCount * 10) * a + bigColumn * deltaCount * 10; 152 | result = { 153 | sourceLevel: elevationLevel, 154 | elevations: [] 155 | }; 156 | for (var i = 0; i <= 10; i++) { 157 | var idx = startSmallIndex; 158 | for (var j = 0; j <= 10; j++) { 159 | var ele = ancestorElevations[idx]; 160 | result.elevations.push(ele); 161 | idx += deltaCount; 162 | } 163 | //遍历完一行之后往下移,startSmallIndex表示一行的左边的起点 164 | startSmallIndex += deltaCount * a; 165 | } 166 | } 167 | } 168 | } 169 | return result; 170 | }; 171 | 172 | //获取线性插值的高程,比如要找E12的估算高程,那么就先找到E10的精确高程,E10的精确高程是从E7中提取的 173 | //即E7(81*81)->E10(11*11)->插值计算E11、E12、E13 174 | Elevation.getLinearElevation = function(level, row, column) { 175 | if (!Utils.isNonNegativeInteger(level)) { 176 | throw "invalid level"; 177 | } 178 | if (!Utils.isNonNegativeInteger(row)) { 179 | throw "invalid row"; 180 | } 181 | if (!Utils.isNonNegativeInteger(column)) { 182 | throw "invalid column"; 183 | } 184 | var result = null; 185 | var elevationLevel = this.getAncestorElevationLevel(level); 186 | var elevationTileGrid = MathUtils.getTileGridAncestor(elevationLevel, level, row, column); 187 | var exactAncestorElevations = this.getExactElevation(elevationTileGrid.level, elevationTileGrid.row, elevationTileGrid.column); 188 | var deltaLevel = level - elevationLevel; 189 | if (exactAncestorElevations) { 190 | result = { 191 | sourceLevel: elevationLevel - 3, 192 | elevations: null 193 | }; 194 | if (deltaLevel == 1) { 195 | result.elevations = this.getLinearElevationFromParent(exactAncestorElevations, level, row, column); 196 | } else if (deltaLevel == 2) { 197 | result.elevations = this.getLinearElevationFromParent2(exactAncestorElevations, level, row, column); 198 | } else if (deltaLevel == 3) { 199 | result.elevations = this.getLinearElevationFromParent3(exactAncestorElevations, level, row, column); 200 | } 201 | } 202 | return result; 203 | }; 204 | 205 | //从直接父节点的高程数据中获取不是很准确的高程数据,比如T11从E10的高程中(10+1)*(10+1)中获取不是很准确的高程 206 | //通过线性插值的方式获取高程,不精确 207 | Elevation.getLinearElevationFromParent = function(parentElevations, level, row, column) { 208 | if (!(Utils.isArray(parentElevations) && parentElevations.length > 0)) { 209 | throw "invalid parentElevations"; 210 | } 211 | if (!Utils.isNonNegativeInteger(level)) { 212 | throw "invalid level"; 213 | } 214 | if (!Utils.isNonNegativeInteger(row)) { 215 | throw "invalid row"; 216 | } 217 | if (!Utils.isNonNegativeInteger(column)) { 218 | throw "invalid column"; 219 | } 220 | //position为切片在直接父切片中的位置 221 | var position = MathUtils.getTilePositionOfParent(level, row, column); 222 | //先从parent中获取6个半行的数据 223 | var elevatios6_6 = []; 224 | var startIndex = 0; 225 | if (position == MathUtils.LEFT_TOP) { 226 | startIndex = 0; 227 | } else if (position == MathUtils.RIGHT_TOP) { 228 | startIndex = 5; 229 | } else if (position == MathUtils.LEFT_BOTTOM) { 230 | startIndex = 11 * 5; 231 | } else if (position == MathUtils.RIGHT_BOTTOM) { 232 | startIndex = 60; 233 | } 234 | var i,j,idx; 235 | for (i = 0; i <= 5; i++) { 236 | idx = startIndex; 237 | for (j = 0; j <= 5; j++) { 238 | var ele = parentElevations[idx]; 239 | elevatios6_6.push(ele); 240 | idx++; 241 | } 242 | //下移一行 243 | startIndex += 11; 244 | } 245 | //此时elevatios6_6表示的(5+1)*(5+1)的高程数据信息 246 | 247 | var eleExact,eleExactTop,eleLinear; 248 | //下面通过对每一行上的6个点数字两两取平均变成11个点数据 249 | var elevations6_11 = []; 250 | for (i = 0; i <= 5; i++) { 251 | for (j = 0; j <= 5; j++) { 252 | idx = 6 * i + j; 253 | eleExact = elevatios6_6[idx]; 254 | if (j > 0) { 255 | eleExactLeft = elevatios6_6[idx - 1]; 256 | eleLinear = (eleExactLeft + eleExact) / 2; 257 | elevations6_11.push(eleLinear); 258 | } 259 | elevations6_11.push(eleExact); 260 | } 261 | } 262 | //此时elevations6_11表示的是(5+1)*(10+1)的高程数据信息,对每行进行了线性插值 263 | 264 | //下面要对每列进行线性插值,使得每列上的6个点数字两两取平均变成11个点数据 265 | var elevations11_11 = []; 266 | for (i = 0; i <= 5; i++) { 267 | for (j = 0; j <= 10; j++) { 268 | idx = 11 * i + j; 269 | eleExact = elevations6_11[idx]; 270 | if (i > 0) { 271 | eleExactTop = elevations6_11[idx - 11]; 272 | eleLinear = (eleExactTop + eleExact) / 2; 273 | elevations11_11[(2 * i - 1) * 11 + j] = eleLinear; 274 | } 275 | elevations11_11[2 * i * 11 + j] = eleExact; 276 | } 277 | } 278 | //此时elevations11_11表示的是(10+1)*(10+1)的高程数据信息 279 | 280 | return elevations11_11; 281 | }; 282 | 283 | //从相隔两级的高程中获取线性插值数据,比如从T10上面获取T12的高程数据 284 | //parent2Elevations是(10+1)*(10+1)的高程数据 285 | //level、row、column是子孙切片的信息 286 | Elevation.getLinearElevationFromParent2 = function(parent2Elevations, level, row, column) { 287 | var parentTileGrid = MathUtils.getTileGridAncestor(level - 1, level, row, column); 288 | var parentElevations = this.getLinearElevationFromParent(parent2Elevations, parentTileGrid.level, parentTileGrid.row, parentTileGrid.column); 289 | var elevations = this.getLinearElevationFromParent(parentElevations, level, row, column); 290 | return elevations; 291 | }; 292 | 293 | //从相隔三级的高程中获取线性插值数据,比如从T10上面获取T13的高程数据 294 | //parent3Elevations是(10+1)*(10+1)的高程数据 295 | //level、row、column是重孙切片的信息 296 | Elevation.getLinearElevationFromParent3 = function(parent3Elevations, level, row, column) { 297 | var parentTileGrid = MathUtils.getTileGridAncestor(level - 1, level, row, column); 298 | var parentElevations = this.getLinearElevationFromParent2(parent3Elevations, parentTileGrid.level, parentTileGrid.row, parentTileGrid.column); 299 | var elevations = this.getLinearElevationFromParent(parentElevations, level, row, column); 300 | return elevations; 301 | }; 302 | return Elevation; 303 | }); -------------------------------------------------------------------------------- /js/world/Enum.js: -------------------------------------------------------------------------------- 1 | define({ 2 | UNKNOWN:"UNKNOWN", 3 | FULL_IN:"FULL_IN", 4 | FULL_OUT:"FULL_OUT", 5 | IN_OUT:"IN_OUT", 6 | NOKIA_TILED_MAP:"NOKIA_TILED_MAP", 7 | Google_TILED_MAP:"Google_TILED_MAP", 8 | OSM_TILED_MAP:"OSM_TILED_MAP", 9 | BLENDED_TILED_MAP:"BLENDED_TILED_MAP", 10 | GLOBE_TILE:"GLOBE_TILE", 11 | TERRAIN_TILE:"TERRAIN_TILE" 12 | }); -------------------------------------------------------------------------------- /js/world/Event.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Math", "world/Vector"], function(Kernel, MathUtils, Vector) { 2 | var EventModule = { 3 | canvas: null, 4 | bMouseDown: false, 5 | dragGeo: null, 6 | previousX: -1, 7 | previousY: -1, 8 | onMouseMoveListener: null, 9 | 10 | bindEvents: function(canvas) { 11 | if (!(canvas instanceof HTMLCanvasElement)) { 12 | throw "invalid canvas: not HTMLCanvasElement"; 13 | } 14 | this.canvas = canvas; 15 | this.onMouseMoveListener = this.onMouseMove.bind(this); 16 | window.addEventListener("resize", this.initLayout.bind(this)); 17 | this.canvas.addEventListener("mousedown", this.onMouseDown.bind(this)); 18 | this.canvas.addEventListener("mouseup", this.onMouseUp.bind(this)); 19 | this.canvas.addEventListener("dblclick", this.onDbClick.bind(this)); 20 | this.canvas.addEventListener("mousewheel", this.onMouseWheel.bind(this)); 21 | this.canvas.addEventListener("DOMMouseScroll", this.onMouseWheel.bind(this)); 22 | document.body.addEventListener("keydown", this.onKeyDown.bind(this)); 23 | }, 24 | 25 | initLayout: function() { 26 | if (this.canvas instanceof HTMLCanvasElement) { 27 | this.canvas.width = document.body.clientWidth; 28 | this.canvas.height = document.body.clientHeight; 29 | if (Kernel.globe) { 30 | Kernel.globe.camera.setAspect(this.canvas.width / this.canvas.height); 31 | Kernel.globe.refresh(); 32 | } 33 | } 34 | }, 35 | 36 | //将地球表面的某一点移动到Canvas上 37 | moveLonLatToCanvas: function(lon, lat, canvasX, canvasY) { 38 | var pickResult = Kernel.globe.camera.getPickCartesianCoordInEarthByCanvas(canvasX, canvasY); 39 | if (pickResult.length > 0) { 40 | var newLonLat = MathUtils.cartesianCoordToGeographic(pickResult[0]); 41 | var newLon = newLonLat[0]; 42 | var newLat = newLonLat[1]; 43 | this.moveGeo(lon, lat, newLon, newLat); 44 | } 45 | }, 46 | 47 | moveGeo: function(oldLon, oldLat, newLon, newLat) { 48 | var camera = Kernel.globe.camera; 49 | var deltaLonRadian = -MathUtils.degreeToRadian(newLon - oldLon); //旋转的经度 50 | var deltaLatRadian = MathUtils.degreeToRadian(newLat - oldLat); //旋转的纬度 51 | camera.worldRotateY(deltaLonRadian); 52 | var lightDir = camera.getLightDirection(); 53 | var plumbVector = this.getPlumbVector(lightDir); 54 | camera.worldRotateByVector(deltaLatRadian, plumbVector); 55 | }, 56 | 57 | onMouseDown: function(event) { 58 | if (Kernel.globe) { 59 | this.bMouseDown = true; 60 | this.previousX = event.layerX || event.offsetX; 61 | this.previousY = event.layerY || event.offsetY; 62 | var pickResult = Kernel.globe.camera.getPickCartesianCoordInEarthByCanvas(this.previousX, this.previousY); 63 | if (pickResult.length > 0) { 64 | this.dragGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); 65 | console.log("单击点三维坐标:(" + pickResult[0].x + "," + pickResult[0].y + "," + pickResult[0].z + ");经纬度坐标:[" + this.dragGeo[0] + "," + this.dragGeo[1] + "]"); 66 | } 67 | this.canvas.addEventListener("mousemove", this.onMouseMoveListener, false); 68 | } 69 | }, 70 | 71 | onMouseMove: function(event) { 72 | var globe = Kernel.globe; 73 | if (globe && this.bMouseDown) { 74 | var currentX = event.layerX || event.offsetX; 75 | var currentY = event.layerY || event.offsetY; 76 | var pickResult = globe.camera.getPickCartesianCoordInEarthByCanvas(currentX, currentY); 77 | if (pickResult.length > 0) { 78 | //鼠标在地球范围内 79 | if (this.dragGeo) { 80 | //鼠标拖动过程中要显示底图 81 | //globe.showAllSubTiledLayerAndTiles(); 82 | var newGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); 83 | this.moveGeo(this.dragGeo[0], this.dragGeo[1], newGeo[0], newGeo[1]); 84 | } else { 85 | //进入地球内部 86 | this.dragGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); 87 | } 88 | this.previousX = currentX; 89 | this.previousY = currentY; 90 | this.canvas.style.cursor = "pointer"; 91 | } else { 92 | //鼠标超出地球范围 93 | this.previousX = -1; 94 | this.previousY = -1; 95 | this.dragGeo = null; 96 | this.canvas.style.cursor = "default"; 97 | } 98 | } 99 | }, 100 | 101 | onMouseUp: function() { 102 | this.bMouseDown = false; 103 | this.previousX = -1; 104 | this.previousY = -1; 105 | this.dragGeo = null; 106 | if (this.canvas instanceof HTMLCanvasElement) { 107 | this.canvas.removeEventListener("mousemove", this.onMouseMoveListener, false); 108 | this.canvas.style.cursor = "default"; 109 | } 110 | }, 111 | 112 | onDbClick: function(event) { 113 | var globe = Kernel.globe; 114 | if (globe) { 115 | var absoluteX = event.layerX || event.offsetX; 116 | var absoluteY = event.layerY || event.offsetY; 117 | var pickResult = globe.camera.getPickCartesianCoordInEarthByCanvas(absoluteX, absoluteY); 118 | globe.setLevel(globe.CURRENT_LEVEL + 1); 119 | if (pickResult.length >= 1) { 120 | var pickVertice = pickResult[0]; 121 | var lonlat = MathUtils.cartesianCoordToGeographic(pickVertice); 122 | var lon = lonlat[0]; 123 | var lat = lonlat[1]; 124 | globe.setLevel(globe.CURRENT_LEVEL + 1); 125 | this.moveLonLatToCanvas(lon, lat, absoluteX, absoluteY); 126 | } 127 | } 128 | }, 129 | 130 | onMouseWheel: function(event) { 131 | var globe = Kernel.globe; 132 | if (!globe) { 133 | return; 134 | } 135 | 136 | var deltaLevel = 0; 137 | var delta; 138 | if (event.wheelDelta) { 139 | //非Firefox 140 | delta = event.wheelDelta; 141 | deltaLevel = parseInt(delta / 120); 142 | } else if (event.detail) { 143 | //Firefox 144 | delta = event.detail; 145 | deltaLevel = -parseInt(delta / 3); 146 | } 147 | var newLevel = globe.CURRENT_LEVEL + deltaLevel; 148 | globe.setLevel(newLevel); 149 | }, 150 | 151 | onKeyDown: function(event) { 152 | var globe = Kernel.globe; 153 | if (!globe) { 154 | return; 155 | } 156 | 157 | var MIN_PITCH = 36; 158 | var DELTA_PITCH = 2; 159 | var camera = globe.camera; 160 | var keyNum = event.keyCode !== undefined ? event.keyCode : event.which; 161 | //上、下、左、右:38、40、37、39 162 | if (keyNum == 38 || keyNum == 40) { 163 | if (keyNum == 38) { 164 | if (camera.pitch <= MIN_PITCH) { 165 | return; 166 | } 167 | } else if (keyNum == 40) { 168 | if (camera.pitch >= 90) { 169 | return; 170 | } 171 | DELTA_PITCH *= -1; 172 | } 173 | 174 | var pickResult = camera.getDirectionIntersectPointWithEarth(); 175 | if (pickResult.length > 0) { 176 | var pIntersect = pickResult[0]; 177 | var pCamera = camera.getPosition(); 178 | var legnth2Intersect = MathUtils.getLengthFromVerticeToVertice(pCamera, pIntersect); 179 | var mat = camera.matrix.copy(); 180 | mat.setColumnTrans(pIntersect.x, pIntersect.y, pIntersect.z); 181 | var DELTA_RADIAN = MathUtils.degreeToRadian(DELTA_PITCH); 182 | mat.localRotateX(DELTA_RADIAN); 183 | var dirZ = mat.getColumnZ().getVector(); 184 | dirZ.setLength(legnth2Intersect); 185 | var pNew = pIntersect.plus(dirZ); 186 | camera.look(pNew, pIntersect); 187 | camera.pitch -= DELTA_PITCH; 188 | globe.refresh(); 189 | } else { 190 | alert("视线与地球无交点"); 191 | } 192 | } 193 | }, 194 | 195 | getPlumbVector: function(direction) { 196 | if (!(direction instanceof Vector)) { 197 | throw "invalid direction: not World.Vector"; 198 | } 199 | var dir = direction.getCopy(); 200 | dir.y = 0; 201 | dir.normalize(); 202 | var plumbVector = new Vector(-dir.z, 0, dir.x); 203 | plumbVector.normalize(); 204 | return plumbVector; 205 | } 206 | }; 207 | return EventModule; 208 | }); -------------------------------------------------------------------------------- /js/world/Globe.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Utils", "world/ShaderContent", "world/WebGLRenderer", "world/PerspectiveCamera", "world/Scene", 2 | "world/TiledLayer", "world/SubTiledLayer", "world/Tile", "world/Image", "world/Event"], 3 | function(Kernel, Utils, ShaderContent, WebGLRenderer, PerspectiveCamera, Scene, TiledLayer, SubTiledLayer, Tile, Image1, Event) { 4 | 5 | var Globe = function(canvas, args) { 6 | if (!(canvas instanceof HTMLCanvasElement)) { 7 | throw "invalid canvas: not HTMLCanvasElement"; 8 | } 9 | args = args || {}; 10 | Kernel.globe = this; 11 | this.MAX_LEVEL = 15; //最大的渲染级别15 12 | this.CURRENT_LEVEL = -1; //当前渲染等级 13 | this.REFRESH_INTERVAL = 300; //Globe自动刷新时间间隔,以毫秒为单位 14 | this.idTimeOut = null; //refresh自定刷新的timeOut的handle 15 | this.renderer = null; 16 | this.scene = null; 17 | this.camera = null; 18 | this.tiledLayer = null; 19 | var vs_content = ShaderContent.SIMPLE_SHADER.VS_CONTENT; 20 | var fs_content = ShaderContent.SIMPLE_SHADER.FS_CONTENT; 21 | this.renderer = Kernel.renderer = new WebGLRenderer(canvas, vs_content, fs_content); 22 | this.scene = new Scene(); 23 | var radio = canvas.width / canvas.height; 24 | this.camera = new PerspectiveCamera(30, radio, 1.0, 20000000.0); 25 | this.renderer.bindScene(this.scene); 26 | this.renderer.bindCamera(this.camera); 27 | this.setLevel(0); 28 | this.renderer.setIfAutoRefresh(true); 29 | Event.initLayout(); 30 | }; 31 | Globe.prototype = { 32 | constructor: Globe, 33 | 34 | setTiledLayer: function(tiledLayer) { 35 | if (!(tiledLayer instanceof TiledLayer)) { 36 | throw "invalid tiledLayer: not World.TiledLayer"; 37 | } 38 | 39 | clearTimeout(this.idTimeOut); 40 | //在更换切片图层的类型时清空缓存的图片 41 | Image1.clear(); 42 | if (this.tiledLayer) { 43 | var b = this.scene.remove(this.tiledLayer); 44 | if (!b) { 45 | console.error("this.scene.remove(this.tiledLayer)失败"); 46 | } 47 | this.scene.tiledLayer = null; 48 | } 49 | this.tiledLayer = tiledLayer; 50 | this.scene.add(this.tiledLayer); 51 | //添加第0级的子图层 52 | var subLayer0 = new SubTiledLayer({ 53 | level: 0 54 | }); 55 | this.tiledLayer.add(subLayer0); 56 | 57 | //要对level为1的图层进行特殊处理,在创建level为1时就创建其中的全部的四个tile 58 | var subLayer1 = new SubTiledLayer({ 59 | level: 1 60 | }); 61 | this.tiledLayer.add(subLayer1); 62 | Kernel.canvas.style.cursor = "wait"; 63 | for (var m = 0; m <= 1; m++) { 64 | for (var n = 0; n <= 1; n++) { 65 | var args = { 66 | level: 1, 67 | row: m, 68 | column: n, 69 | url: "" 70 | }; 71 | args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); 72 | var tile = new Tile(args); 73 | subLayer1.add(tile); 74 | } 75 | } 76 | Kernel.canvas.style.cursor = "default"; 77 | this.tick(); 78 | }, 79 | 80 | setLevel: function(level) { 81 | if (!Utils.isInteger(level)) { 82 | throw "invalid level"; 83 | } 84 | if (level < 0) { 85 | return; 86 | } 87 | level = level > this.MAX_LEVEL ? this.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 88 | if (level != this.CURRENT_LEVEL) { 89 | if (this.camera instanceof PerspectiveCamera) { 90 | //要先执行camera.setLevel,然后再刷新 91 | this.camera.setLevel(level); 92 | this.refresh(); 93 | } 94 | } 95 | }, 96 | 97 | /** 98 | * 返回当前的各种矩阵信息:视点矩阵、投影矩阵、两者乘积,以及前三者的逆矩阵 99 | * @returns {{View: null, _View: null, Proj: null, _Proj: null, ProjView: null, _View_Proj: null}} 100 | * @private 101 | */ 102 | _getMatrixInfo: function() { 103 | var options = { 104 | View: null, //视点矩阵 105 | _View: null, //视点矩阵的逆矩阵 106 | Proj: null, //投影矩阵 107 | _Proj: null, //投影矩阵的逆矩阵 108 | ProjView: null, //投影矩阵与视点矩阵的乘积 109 | _View_Proj: null //视点逆矩阵与投影逆矩阵的乘积 110 | }; 111 | options.View = this.getViewMatrix(); 112 | options._View = options.View.getInverseMatrix(); 113 | options.Proj = this.projMatrix; 114 | options._Proj = options.Proj.getInverseMatrix(); 115 | options.ProjView = options.Proj.multiplyMatrix(options.View); 116 | options._View_Proj = options.ProjView.getInverseMatrix(); 117 | return options; 118 | }, 119 | 120 | tick: function() { 121 | var globe = Kernel.globe; 122 | if (globe) { 123 | globe.refresh(); 124 | this.idTimeOut = setTimeout(globe.tick, globe.REFRESH_INTERVAL); 125 | } 126 | }, 127 | 128 | refresh: function() { 129 | if (!this.tiledLayer || !this.scene || !this.camera) { 130 | return; 131 | } 132 | var level = this.CURRENT_LEVEL + 3; 133 | this.tiledLayer.updateSubLayerCount(level); 134 | var projView = this.camera.getProjViewMatrix(); 135 | var options = { 136 | projView: projView, 137 | threshold: 1 138 | }; 139 | options.threshold = Math.min(90 / this.camera.pitch, 1.5); 140 | //最大级别的level所对应的可见TileGrids 141 | var lastLevelTileGrids = this.camera.getVisibleTilesByLevel(level, options); 142 | var levelsTileGrids = []; //level-2 143 | var parentTileGrids = lastLevelTileGrids; 144 | var i; 145 | for (i = level; i >= 2; i--) { 146 | levelsTileGrids.push(parentTileGrids); //此行代码表示第i层级的可见切片 147 | parentTileGrids = Utils.map(parentTileGrids, function(item) { 148 | return item.getParent(); 149 | }); 150 | parentTileGrids = Utils.filterRepeatArray(parentTileGrids); 151 | } 152 | levelsTileGrids.reverse(); //2-level 153 | for (i = 2; i <= level; i++) { 154 | var subLevel = i; 155 | var subLayer = this.tiledLayer.children[subLevel]; 156 | subLayer.updateTiles(levelsTileGrids[0], true); 157 | levelsTileGrids.splice(0, 1); 158 | } 159 | if (Kernel.TERRAIN_ENABLED) { 160 | this.requestElevationsAndCheckTerrain(); 161 | } 162 | }, 163 | 164 | //请求更新高程数据,并检测Terrain 165 | requestElevationsAndCheckTerrain: function() { 166 | var level = this.tiledLayer.children.length - 1; 167 | //当level>7时请求更新高程数据 168 | //请求的数据与第7级的切片大小相同 169 | //if(level > Kernel.ELEVATION_LEVEL){ 170 | 171 | //达到TERRAIN_LEVEL级别时考虑三维请求 172 | if (level >= Kernel.TERRAIN_LEVEL) { 173 | for (var i = Kernel.ELEVATION_LEVEL + 1; i <= level; i++) { 174 | var subLayer = this.tiledLayer.children[i]; 175 | subLayer.requestElevations(); 176 | //检查SubTiledLayer下的子图层是否符合转换成TerrainTile的条件,如果适合就自动以三维地形图显示 177 | if (i >= Kernel.TERRAIN_LEVEL) { 178 | subLayer.checkTerrain(); 179 | } 180 | } 181 | } 182 | } 183 | }; 184 | return Globe; 185 | }); -------------------------------------------------------------------------------- /js/world/GoogleTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/TiledLayer"], function(TiledLayer) { 2 | //Google 3 | var GoogleTiledLayer = function(args) { 4 | TiledLayer.apply(this, arguments); 5 | }; 6 | GoogleTiledLayer.prototype = new TiledLayer(); 7 | GoogleTiledLayer.prototype.constructor = GoogleTiledLayer; 8 | GoogleTiledLayer.prototype.getImageUrl = function(level, row, column) { 9 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 10 | var sum = level + row + column; 11 | var idx = 1 + sum % 3; 12 | var url = "//mt" + idx + ".google.cn/vt/lyrs=m@212000000&hl=zh-CN&gl=CN&src=app&x=" + column + "&y=" + row + "&z=" + level + "&s=Galil"; 13 | return url; 14 | }; 15 | return GoogleTiledLayer; 16 | }); -------------------------------------------------------------------------------- /js/world/Image.js: -------------------------------------------------------------------------------- 1 | define(["world/Utils"], function(Utils) { 2 | //缓存图片信息1、2、3、4级的图片信息 3 | var Image1 = { 4 | MAX_LEVEL: 4, //缓存图片的最大level 5 | images: {} 6 | }; 7 | Image1.add = function(url, img) { 8 | if (!Utils.isString(url)) { 9 | throw "invalid url: not string"; 10 | } 11 | if (!(img instanceof HTMLImageElement)) { 12 | throw "invalid img: not HTMLImageElement"; 13 | } 14 | this.images[url] = img; 15 | }; 16 | Image1.get = function(url) { 17 | if (!Utils.isString(url)) { 18 | throw "invalid url: not string"; 19 | } 20 | return this.images[url]; 21 | }; 22 | Image1.remove = function(url) { 23 | if (!(Utils.isString(url))) { 24 | throw "invalid url: not string"; 25 | } 26 | delete this.images[url]; 27 | }; 28 | Image1.clear = function() { 29 | this.images = {}; 30 | }; 31 | Image1.getCount = function() { 32 | var count = 0; 33 | for (var url in this.images) { 34 | if (this.images.hasOwnProperty(url)) { 35 | count++; 36 | } 37 | } 38 | return count; 39 | }; 40 | return Image1; 41 | }); -------------------------------------------------------------------------------- /js/world/Kernel.js: -------------------------------------------------------------------------------- 1 | define({ 2 | gl: null, 3 | canvas: null, 4 | idCounter: 0, //Object3D对象的唯一标识 5 | renderer: null, 6 | globe: null, 7 | BASE_LEVEL: 6, //渲染的基准层级 8 | EARTH_RADIUS: 6378137, 9 | MAX_PROJECTED_COORD: 20037508.3427892, 10 | ELEVATION_LEVEL: 7, //开始获取高程数据 11 | TERRAIN_LEVEL: 10, //开始显示三维地形 12 | TERRAIN_ENABLED: false, //是否启用三维地形 13 | TERRAIN_PITCH: 80, //开始显示三维地形的pich 14 | proxy: "" 15 | }); -------------------------------------------------------------------------------- /js/world/Line.js: -------------------------------------------------------------------------------- 1 | define(["world/Vertice", "world/Vector"], function(Vertice, Vector) { 2 | var Line = function(position, direction) { 3 | if (!(position instanceof Vertice)) { 4 | throw "invalid position"; 5 | } 6 | if (!(direction instanceof Vector)) { 7 | throw "invalid direction"; 8 | } 9 | this.vertice = position.getCopy(); 10 | this.vector = direction.getCopy(); 11 | this.vector.normalize(); 12 | }; 13 | Line.prototype.constructor = Line; 14 | Line.prototype.setVertice = function(position) { 15 | if (!(position instanceof Vertice)) { 16 | throw "invalid position"; 17 | } 18 | this.vertice = position.getCopy(); 19 | return this; 20 | }; 21 | Line.prototype.setVector = function(direction) { 22 | if (!(direction instanceof Vector)) { 23 | throw "invalid direction"; 24 | } 25 | this.vector = direction.getCopy(); 26 | this.vector.normalize(); 27 | return this; 28 | }; 29 | Line.prototype.getCopy = function() { 30 | var lineCopy = new Line(this.vertice, this.vector); 31 | return lineCopy; 32 | }; 33 | return Line; 34 | }); -------------------------------------------------------------------------------- /js/world/Matrix.js: -------------------------------------------------------------------------------- 1 | define(["require", "world/Utils", "world/Vertice", "world/Vector"], function(require, Utils, Vertice, Vector) { 2 | 3 | var Matrix = function(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44) { 4 | this.elements = new Float32Array(16); 5 | 6 | this.setElements( 7 | (m11 === undefined ? 1 : m11), (m12 === undefined ? 0 : m12), (m13 === undefined ? 0 : m13), (m14 === undefined ? 0 : m14), 8 | (m21 === undefined ? 0 : m21), (m22 === undefined ? 1 : m22), (m23 === undefined ? 0 : m23), (m24 === undefined ? 0 : m24), 9 | (m31 === undefined ? 0 : m31), (m32 === undefined ? 0 : m32), (m33 === undefined ? 1 : m33), (m34 === undefined ? 0 : m34), 10 | (m41 === undefined ? 0 : m41), (m42 === undefined ? 0 : m42), (m43 === undefined ? 0 : m43), (m44 === undefined ? 1 : m44) 11 | ); 12 | }; 13 | 14 | Matrix.prototype = { 15 | constructor: Matrix, 16 | 17 | setElements: function(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44) { 18 | var count = arguments.length; 19 | if (count < 16) { 20 | throw "invalid arguments:arguments length error"; 21 | } 22 | for (var i = 0; i < count; i++) { 23 | if (!Utils.isNumber(arguments[i])) { 24 | throw "invalid arguments[" + i + "]"; 25 | } 26 | } 27 | var values = this.elements; 28 | values[0] = m11; 29 | values[4] = m12; 30 | values[8] = m13; 31 | values[12] = m14; 32 | values[1] = m21; 33 | values[5] = m22; 34 | values[9] = m23; 35 | values[13] = m24; 36 | values[2] = m31; 37 | values[6] = m32; 38 | values[10] = m33; 39 | values[14] = m34; 40 | values[3] = m41; 41 | values[7] = m42; 42 | values[11] = m43; 43 | values[15] = m44; 44 | return this; 45 | }, 46 | 47 | setColumnX: function(x, y, z) { 48 | if (!Utils.isNumber(x)) { 49 | throw "invalid x"; 50 | } 51 | if (!Utils.isNumber(y)) { 52 | throw "invalid y"; 53 | } 54 | if (!Utils.isNumber(z)) { 55 | throw "invalid z"; 56 | } 57 | this.elements[0] = x; 58 | this.elements[1] = y; 59 | this.elements[2] = z; 60 | }, 61 | 62 | getColumnX: function() { 63 | return new Vertice(this.elements[0], this.elements[1], this.elements[2]); 64 | }, 65 | 66 | setColumnY: function(x, y, z) { 67 | if (!Utils.isNumber(x)) { 68 | throw "invalid x"; 69 | } 70 | if (!Utils.isNumber(y)) { 71 | throw "invalid y"; 72 | } 73 | if (!Utils.isNumber(z)) { 74 | throw "invalid z"; 75 | } 76 | this.elements[4] = x; 77 | this.elements[5] = y; 78 | this.elements[6] = z; 79 | }, 80 | 81 | getColumnY: function() { 82 | return new Vertice(this.elements[4], this.elements[5], this.elements[6]); 83 | }, 84 | 85 | setColumnZ: function(x, y, z) { 86 | if (!Utils.isNumber(x)) { 87 | throw "invalid x"; 88 | } 89 | if (!Utils.isNumber(y)) { 90 | throw "invalid y"; 91 | } 92 | if (!Utils.isNumber(z)) { 93 | throw "invalid z"; 94 | } 95 | this.elements[8] = x; 96 | this.elements[9] = y; 97 | this.elements[10] = z; 98 | }, 99 | 100 | getColumnZ: function() { 101 | return new Vertice(this.elements[8], this.elements[9], this.elements[10]); 102 | }, 103 | 104 | setColumnTrans: function(x, y, z) { 105 | if (!Utils.isNumber(x)) { 106 | throw "invalid x"; 107 | } 108 | if (!Utils.isNumber(y)) { 109 | throw "invalid y"; 110 | } 111 | if (!Utils.isNumber(z)) { 112 | throw "invalid z"; 113 | } 114 | this.elements[12] = x; 115 | this.elements[13] = y; 116 | this.elements[14] = z; 117 | }, 118 | 119 | getColumnTrans: function() { 120 | return new Vertice(this.elements[12], this.elements[13], this.elements[14]); 121 | }, 122 | 123 | setLastRowDefault: function() { 124 | this.elements[3] = 0; 125 | this.elements[7] = 0; 126 | this.elements[11] = 0; 127 | this.elements[15] = 1; 128 | }, 129 | 130 | //对当前矩阵进行转置,并对当前矩阵产生影响 131 | transpose: function() { 132 | var result = this.getTransposeMatrix(); 133 | this.setMatrixByOther(result); 134 | }, 135 | 136 | //返回当前矩阵的转置矩阵,不对当前矩阵产生影响 137 | getTransposeMatrix: function() { 138 | var result = new Matrix(); 139 | result.elements[0] = this.elements[0]; 140 | result.elements[4] = this.elements[1]; 141 | result.elements[8] = this.elements[2]; 142 | result.elements[12] = this.elements[3]; 143 | 144 | result.elements[1] = this.elements[4]; 145 | result.elements[5] = this.elements[5]; 146 | result.elements[9] = this.elements[6]; 147 | result.elements[13] = this.elements[7]; 148 | 149 | result.elements[2] = this.elements[8]; 150 | result.elements[6] = this.elements[9]; 151 | result.elements[10] = this.elements[10]; 152 | result.elements[14] = this.elements[11]; 153 | 154 | result.elements[3] = this.elements[12]; 155 | result.elements[7] = this.elements[13]; 156 | result.elements[11] = this.elements[14]; 157 | result.elements[15] = this.elements[15]; 158 | return result; 159 | }, 160 | 161 | //对当前矩阵进行取逆操作,并对当前矩阵产生影响 162 | inverse: function() { 163 | var result = this.getInverseMatrix(); 164 | this.setMatrixByOther(result); 165 | }, 166 | 167 | //返回当前矩阵的逆矩阵,不对当前矩阵产生影响 168 | getInverseMatrix: function() { 169 | var a = this.elements; 170 | var result = new Matrix(); 171 | var b = result.elements; 172 | var c = a[0], 173 | d = a[1], 174 | e = a[2], 175 | g = a[3], 176 | f = a[4], 177 | h = a[5], 178 | i = a[6], 179 | j = a[7], 180 | k = a[8], 181 | l = a[9], 182 | n = a[10], 183 | o = a[11], 184 | m = a[12], 185 | p = a[13], 186 | r = a[14], 187 | s = a[15]; 188 | var A = c * h - d * f; 189 | var B = c * i - e * f; 190 | var t = c * j - g * f; 191 | var u = d * i - e * h; 192 | var v = d * j - g * h; 193 | var w = e * j - g * i; 194 | var x = k * p - l * m; 195 | var y = k * r - n * m; 196 | var z = k * s - o * m; 197 | var C = l * r - n * p; 198 | var D = l * s - o * p; 199 | var E = n * s - o * r; 200 | var q = A * E - B * D + t * C + u * z - v * y + w * x; 201 | if (!q) return null; 202 | q = 1 / q; 203 | b[0] = (h * E - i * D + j * C) * q; 204 | b[1] = (-d * E + e * D - g * C) * q; 205 | b[2] = (p * w - r * v + s * u) * q; 206 | b[3] = (-l * w + n * v - o * u) * q; 207 | b[4] = (-f * E + i * z - j * y) * q; 208 | b[5] = (c * E - e * z + g * y) * q; 209 | b[6] = (-m * w + r * t - s * B) * q; 210 | b[7] = (k * w - n * t + o * B) * q; 211 | b[8] = (f * D - h * z + j * x) * q; 212 | b[9] = (-c * D + d * z - g * x) * q; 213 | b[10] = (m * v - p * t + s * A) * q; 214 | b[11] = (-k * v + l * t - o * A) * q; 215 | b[12] = (-f * C + h * y - i * x) * q; 216 | b[13] = (c * C - d * y + e * x) * q; 217 | b[14] = (-m * u + p * B - r * A) * q; 218 | b[15] = (k * u - l * B + n * A) * q; 219 | return result; 220 | }, 221 | 222 | setMatrixByOther: function(otherMatrix) { 223 | if (!(otherMatrix instanceof Matrix)) { 224 | throw "invalid otherMatrix"; 225 | } 226 | for (var i = 0; i < otherMatrix.elements.length; i++) { 227 | this.elements[i] = otherMatrix.elements[i]; 228 | } 229 | }, 230 | 231 | /** 232 | * 将矩阵设置为单位阵 233 | */ 234 | setUnitMatrix: function() { 235 | this.setElements(1, 0, 0, 0, 236 | 0, 1, 0, 0, 237 | 0, 0, 1, 0, 238 | 0, 0, 0, 1); 239 | }, 240 | 241 | /** 242 | * 判断矩阵是否为单位阵 243 | * @returns {boolean} 244 | */ 245 | isUnitMatrix: function() { 246 | var values = this.elements; 247 | for (var i = 0; i < values.length; i++) { 248 | if (i % 4 === 0) { 249 | if (values[i] != 1) { 250 | //斜对角线上的值需要为1 251 | return false; 252 | } 253 | } else { 254 | if (values[i] !== 0) { 255 | //非斜对角线上的值需要为0 256 | return false; 257 | } 258 | } 259 | } 260 | return true; 261 | }, 262 | 263 | copy: function() { 264 | return new Matrix(this.elements[0], this.elements[4], this.elements[8], this.elements[12], 265 | this.elements[1], this.elements[5], this.elements[9], this.elements[13], 266 | this.elements[2], this.elements[6], this.elements[10], this.elements[14], 267 | this.elements[3], this.elements[7], this.elements[11], this.elements[15]); 268 | }, 269 | 270 | multiplyMatrix: function(otherMatrix) { 271 | if (!(otherMatrix instanceof Matrix)) { 272 | throw "invalid otherMatrix"; 273 | } 274 | var values1 = this.elements; 275 | var values2 = otherMatrix.elements; 276 | var m11 = values1[0] * values2[0] + values1[4] * values2[1] + values1[8] * values2[2] + values1[12] * values2[3]; 277 | var m12 = values1[0] * values2[4] + values1[4] * values2[5] + values1[8] * values2[6] + values1[12] * values2[7]; 278 | var m13 = values1[0] * values2[8] + values1[4] * values2[9] + values1[8] * values2[10] + values1[12] * values2[11]; 279 | var m14 = values1[0] * values2[12] + values1[4] * values2[13] + values1[8] * values2[14] + values1[12] * values2[15]; 280 | var m21 = values1[1] * values2[0] + values1[5] * values2[1] + values1[9] * values2[2] + values1[13] * values2[3]; 281 | var m22 = values1[1] * values2[4] + values1[5] * values2[5] + values1[9] * values2[6] + values1[13] * values2[7]; 282 | var m23 = values1[1] * values2[8] + values1[5] * values2[9] + values1[9] * values2[10] + values1[13] * values2[11]; 283 | var m24 = values1[1] * values2[12] + values1[5] * values2[13] + values1[9] * values2[14] + values1[13] * values2[15]; 284 | var m31 = values1[2] * values2[0] + values1[6] * values2[1] + values1[10] * values2[2] + values1[14] * values2[3]; 285 | var m32 = values1[2] * values2[4] + values1[6] * values2[5] + values1[10] * values2[6] + values1[14] * values2[7]; 286 | var m33 = values1[2] * values2[8] + values1[6] * values2[9] + values1[10] * values2[10] + values1[14] * values2[11]; 287 | var m34 = values1[2] * values2[12] + values1[6] * values2[13] + values1[10] * values2[14] + values1[14] * values2[15]; 288 | var m41 = values1[3] * values2[0] + values1[7] * values2[1] + values1[11] * values2[2] + values1[15] * values2[3]; 289 | var m42 = values1[3] * values2[4] + values1[7] * values2[5] + values1[11] * values2[6] + values1[15] * values2[7]; 290 | var m43 = values1[3] * values2[8] + values1[7] * values2[9] + values1[11] * values2[10] + values1[15] * values2[11]; 291 | var m44 = values1[3] * values2[12] + values1[7] * values2[13] + values1[11] * values2[14] + values1[15] * values2[15]; 292 | return new Matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); 293 | }, 294 | 295 | /** 296 | * 计算矩阵与列向量的乘积 297 | * @param c 四元数组 298 | * @return {Matrix} 列向量,四元数组 299 | */ 300 | multiplyColumn: function(c) { 301 | var valid = Utils.isArray(c) && c.length == 4; 302 | if (!valid) { 303 | throw "invalid c"; 304 | } 305 | var values1 = this.elements; 306 | var values2 = c; 307 | var m11 = values1[0] * values2[0] + values1[4] * values2[1] + values1[8] * values2[2] + values1[12] * values2[3]; 308 | var m21 = values1[1] * values2[0] + values1[5] * values2[1] + values1[9] * values2[2] + values1[13] * values2[3]; 309 | var m31 = values1[2] * values2[0] + values1[6] * values2[1] + values1[10] * values2[2] + values1[14] * values2[3]; 310 | var m41 = values1[3] * values2[0] + values1[7] * values2[1] + values1[11] * values2[2] + values1[15] * values2[3]; 311 | return [m11, m21, m31, m41]; 312 | }, 313 | 314 | divide: function(a) { 315 | if (!Utils.isNumber(a)) { 316 | throw "invalid a:a is not number"; 317 | } 318 | if (a === 0) { 319 | throw "invalid a:a is 0"; 320 | } 321 | if (a !== 0) { 322 | for (var i = 0, length = this.elements.length; i < length; i++) { 323 | this.elements[i] /= a; 324 | } 325 | } 326 | }, 327 | 328 | worldTranslate: function(x, y, z) { 329 | if (!Utils.isNumber(x)) { 330 | throw "invalid x"; 331 | } 332 | if (!Utils.isNumber(y)) { 333 | throw "invalid y"; 334 | } 335 | if (!Utils.isNumber(z)) { 336 | throw "invalid z"; 337 | } 338 | this.elements[12] += x; 339 | this.elements[13] += y; 340 | this.elements[14] += z; 341 | }, 342 | 343 | localTranslate: function(x, y, z) { 344 | if (!Utils.isNumber(x)) { 345 | throw "invalid x"; 346 | } 347 | if (!Utils.isNumber(y)) { 348 | throw "invalid y"; 349 | } 350 | if (!Utils.isNumber(z)) { 351 | throw "invalid z"; 352 | } 353 | var localColumn = [x, y, z, 1]; 354 | var worldColumn = this.multiplyColumn(localColumn); 355 | var origin = this.getPosition(); 356 | this.worldTranslate(worldColumn[0] - origin.x, worldColumn[1] - origin.y, worldColumn[2] - origin.z); 357 | }, 358 | 359 | worldScale: function(scaleX, scaleY, scaleZ) { 360 | scaleX = (scaleX !== undefined) ? scaleX : 1; 361 | scaleY = (scaleY !== undefined) ? scaleY : 1; 362 | scaleZ = (scaleZ !== undefined) ? scaleZ : 1; 363 | if (!Utils.isNumber(scaleX)) { 364 | throw "invalid x"; 365 | } 366 | if (!Utils.isNumber(scaleY)) { 367 | throw "invalid y"; 368 | } 369 | if (!Utils.isNumber(scaleZ)) { 370 | throw "invalid z"; 371 | } 372 | var m = new Matrix(scaleX, 0, 0, 0, 373 | 0, scaleY, 0, 0, 374 | 0, 0, scaleZ, 0, 375 | 0, 0, 0, 1); 376 | var result = m.multiplyMatrix(this); 377 | this.setMatrixByOther(result); 378 | }, 379 | 380 | localScale: function(scaleX, scaleY, scaleZ) { 381 | var transVertice = this.getColumnTrans(); 382 | this.setColumnTrans(0, 0, 0); 383 | this.worldScale(scaleX, scaleY, scaleZ); 384 | this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); 385 | }, 386 | 387 | worldRotateX: function(radian) { 388 | if (!Utils.isNumber(radian)) { 389 | throw "invalid radian"; 390 | } 391 | var c = Math.cos(radian); 392 | var s = Math.sin(radian); 393 | var m = new Matrix(1, 0, 0, 0, 394 | 0, c, -s, 0, 395 | 0, s, c, 0, 396 | 0, 0, 0, 1); 397 | var result = m.multiplyMatrix(this); 398 | this.setMatrixByOther(result); 399 | }, 400 | 401 | worldRotateY: function(radian) { 402 | if (!Utils.isNumber(radian)) { 403 | throw "invalid radian:not number"; 404 | } 405 | var c = Math.cos(radian); 406 | var s = Math.sin(radian); 407 | var m = new Matrix(c, 0, s, 0, 408 | 0, 1, 0, 0, -s, 0, c, 0, 409 | 0, 0, 0, 1); 410 | var result = m.multiplyMatrix(this); 411 | this.setMatrixByOther(result); 412 | }, 413 | 414 | worldRotateZ: function(radian) { 415 | if (!Utils.isNumber(radian)) { 416 | throw "invalid radian:not number"; 417 | } 418 | var c = Math.cos(radian); 419 | var s = Math.sin(radian); 420 | var m = new Matrix(c, -s, 0, 0, 421 | s, c, 0, 0, 422 | 0, 0, 1, 0, 423 | 0, 0, 0, 1); 424 | var result = m.multiplyMatrix(this); 425 | this.setMatrixByOther(result); 426 | }, 427 | 428 | worldRotateByVector: function(radian, vector) { 429 | if (!Utils.isNumber(radian)) { 430 | throw "invalid radian:not number"; 431 | } 432 | if (!(vector instanceof Vector)) { 433 | throw "invalid vector:not Vector"; 434 | } 435 | var x = vector.x; 436 | var y = vector.y; 437 | var z = vector.z; 438 | 439 | var length, s, c; 440 | var xx, yy, zz, xy, yz, zx, xs, ys, zs, one_c; 441 | 442 | s = Math.sin(radian); 443 | c = Math.cos(radian); 444 | 445 | length = Math.sqrt(x * x + y * y + z * z); 446 | 447 | // Rotation matrix is normalized 448 | x /= length; 449 | y /= length; 450 | z /= length; 451 | 452 | xx = x * x; 453 | yy = y * y; 454 | zz = z * z; 455 | xy = x * y; 456 | yz = y * z; 457 | zx = z * x; 458 | xs = x * s; 459 | ys = y * s; 460 | zs = z * s; 461 | one_c = 1.0 - c; 462 | 463 | var m11 = (one_c * xx) + c; //M(0,0) 464 | var m12 = (one_c * xy) - zs; //M(0,1) 465 | var m13 = (one_c * zx) + ys; //M(0,2) 466 | var m14 = 0.0; //M(0,3) 表示平移X 467 | 468 | var m21 = (one_c * xy) + zs; //M(1,0) 469 | var m22 = (one_c * yy) + c; //M(1,1) 470 | var m23 = (one_c * yz) - xs; //M(1,2) 471 | var m24 = 0.0; //M(1,3) 表示平移Y 472 | 473 | var m31 = (one_c * zx) - ys; //M(2,0) 474 | var m32 = (one_c * yz) + xs; //M(2,1) 475 | var m33 = (one_c * zz) + c; //M(2,2) 476 | var m34 = 0.0; //M(2,3) 表示平移Z 477 | 478 | var m41 = 0.0; //M(3,0) 479 | var m42 = 0.0; //M(3,1) 480 | var m43 = 0.0; //M(3,2) 481 | var m44 = 1.0; //M(3,3) 482 | 483 | var mat = new Matrix(m11, m12, m13, m14, 484 | m21, m22, m23, m24, 485 | m31, m32, m33, m34, 486 | m41, m42, m43, m44); 487 | var result = mat.multiplyMatrix(this); 488 | this.setMatrixByOther(result); 489 | }, 490 | 491 | localRotateX: function(radian) { 492 | if (!Utils.isNumber(radian)) { 493 | throw "invalid radian:not number"; 494 | } 495 | var transVertice = this.getColumnTrans(); 496 | this.setColumnTrans(0, 0, 0); 497 | var columnX = this.getColumnX().getVector(); 498 | this.worldRotateByVector(radian, columnX); 499 | this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); 500 | }, 501 | 502 | localRotateY: function(radian) { 503 | if (!Utils.isNumber(radian)) { 504 | throw "invalid radian:not number"; 505 | } 506 | var transVertice = this.getColumnTrans(); 507 | this.setColumnTrans(0, 0, 0); 508 | var columnY = this.getColumnY().getVector(); 509 | this.worldRotateByVector(radian, columnY); 510 | this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); 511 | }, 512 | 513 | localRotateZ: function(radian) { 514 | if (!Utils.isNumber(radian)) { 515 | throw "invalid radian:not number"; 516 | } 517 | var transVertice = this.getColumnTrans(); 518 | this.setColumnTrans(0, 0, 0); 519 | var columnZ = this.getColumnZ().getVector(); 520 | this.worldRotateByVector(radian, columnZ); 521 | this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); 522 | }, 523 | 524 | //localVector指的是相对于模型坐标系中的向量 525 | localRotateByVector: function(radian, localVector) { 526 | if (!Utils.isNumber(radian)) { 527 | throw "invalid radian: not number"; 528 | } 529 | if (!(localVector instanceof Vector)) { 530 | throw "invalid localVector: not Vector"; 531 | } 532 | var localColumn = localVector.getArray(); 533 | localColumn.push(1); //四元数组 534 | var worldColumn = this.multiplyColumn(localColumn); //模型坐标转换为世界坐标 535 | var worldVector = new Vector(worldColumn[0], worldColumn[1], worldColumn[2]); 536 | 537 | var transVertice = this.getColumnTrans(); 538 | this.setColumnTrans(0, 0, 0); 539 | this.worldRotateByVector(radian, worldVector); 540 | this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); 541 | } 542 | }; 543 | 544 | return Matrix; 545 | }); -------------------------------------------------------------------------------- /js/world/NokiaTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/TiledLayer"], function(TiledLayer) { 2 | var NokiaTiledLayer = function(args) { 3 | TiledLayer.apply(this, arguments); 4 | }; 5 | NokiaTiledLayer.prototype = new TiledLayer(); 6 | NokiaTiledLayer.prototype.constructor = NokiaTiledLayer; 7 | NokiaTiledLayer.prototype.getImageUrl = function(level, row, column) { 8 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 9 | var sum = level + row + column; 10 | var idx = 1 + sum % 4; //1,2,3,4 11 | //https://1.base.maps.api.here.com/maptile/2.1/maptile/2ae1d8fbb0/normal.day/4/9/7/512/png8?app_id=xWVIueSv6JL0aJ5xqTxb&app_code=djPZyynKsbTjIUDOBcHZ2g&lg=eng&ppi=72&pview=DEF 12 | var url = "//"+idx+".base.maps.api.here.com/maptile/2.1/maptile/2ae1d8fbb0/normal.day/"+level+"/"+column+"/"+row+"/512/png8?app_id=xWVIueSv6JL0aJ5xqTxb&app_code=djPZyynKsbTjIUDOBcHZ2g&lg=eng&ppi=72&pview=DEF"; 13 | return url; 14 | }; 15 | return NokiaTiledLayer; 16 | }); -------------------------------------------------------------------------------- /js/world/Object3D.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Matrix", "world/TextureMaterial"], 2 | function(Kernel, Matrix, TextureMaterial) { 3 | /** 4 | * 三维对象的基类 5 | * @param args 6 | * @constructor 7 | */ 8 | var Object3D = function(args) { 9 | this.id = ++Kernel.idCounter; 10 | this.matrix = new Matrix(); 11 | this.parent = null; 12 | this.vertices = []; 13 | this.vertexBuffer = null; 14 | this.indices = []; 15 | this.indexBuffer = null; 16 | this.textureCoords = []; 17 | this.textureCoordBuffer = null; 18 | this.material = null; 19 | this.visible = true; 20 | if (args && args.material) { 21 | this.material = args.material; 22 | } 23 | this.createVerticeData(args); 24 | }; 25 | Object3D.prototype = { 26 | constructor: Object3D, 27 | 28 | /** 29 | * 根据传入的参数生成vertices和indices,然后通过调用setBuffers初始化buffer 30 | * @param params 传入的参数 31 | */ 32 | createVerticeData: function(params) { 33 | /*var infos = { 34 | vertices:vertices, 35 | indices:indices 36 | }; 37 | this.setBuffers(infos);*/ 38 | }, 39 | 40 | /** 41 | * 设置buffer,由createVerticeData函数调用 42 | * @param infos 包含vertices和indices信息,由createVerticeData传入参数 43 | */ 44 | setBuffers: function(infos) { 45 | if (infos) { 46 | this.vertices = infos.vertices || []; 47 | this.indices = infos.indices || []; 48 | this.textureCoords = infos.textureCoords || []; 49 | if (this.vertices.length > 0 && this.indices.length > 0) { 50 | if (!(gl.isBuffer(this.vertexBuffer))) { 51 | this.vertexBuffer = gl.createBuffer(); 52 | } 53 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); 54 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.vertices), gl.STATIC_DRAW); 55 | 56 | if (!(gl.isBuffer(this.indexBuffer))) { 57 | this.indexBuffer = gl.createBuffer(); 58 | } 59 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); 60 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), gl.STATIC_DRAW); 61 | } 62 | 63 | //使用纹理 64 | if (this.material instanceof TextureMaterial) { 65 | if (this.textureCoords.length > 0) { //提供了纹理坐标 66 | if (!(gl.isBuffer(this.textureCoordBuffer))) { 67 | this.textureCoordBuffer = gl.createBuffer(); 68 | } 69 | gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordBuffer); 70 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.textureCoords), gl.STATIC_DRAW); 71 | } 72 | } 73 | } 74 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 75 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 76 | }, 77 | 78 | setShaderMatrix: function(camera) { 79 | // if (!(camera instanceof PerspectiveCamera)) { 80 | // throw "invalid camera : not World.PerspectiveCamera"; 81 | // } 82 | camera.viewMatrix = (camera.viewMatrix instanceof Matrix) ? camera.viewMatrix : camera.getViewMatrix(); 83 | var mvMatrix = camera.viewMatrix.multiplyMatrix(this.matrix); 84 | gl.uniformMatrix4fv(gl.shaderProgram.uMVMatrix, false, mvMatrix.elements); 85 | gl.uniformMatrix4fv(gl.shaderProgram.uPMatrix, false, camera.projMatrix.elements); 86 | }, 87 | 88 | draw: function(camera) { 89 | // if (!(camera instanceof PerspectiveCamera)) { 90 | // throw "invalid camera : not World.PerspectiveCamera"; 91 | // } 92 | if (this.visible) { 93 | if (this.material instanceof TextureMaterial && this.material.loaded) { 94 | gl.enableVertexAttribArray(gl.shaderProgram.aTextureCoord); 95 | gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordBuffer); 96 | gl.vertexAttribPointer(gl.shaderProgram.aTextureCoord, 2, gl.FLOAT, false, 0, 0); 97 | 98 | gl.activeTexture(gl.TEXTURE0); 99 | gl.bindTexture(gl.TEXTURE_2D, this.material.texture); 100 | gl.uniform1i(gl.shaderProgram.uSampler, 0); 101 | 102 | this.setShaderMatrix(camera); 103 | 104 | //往shader中对vertex赋值 105 | gl.enableVertexAttribArray(gl.shaderProgram.aVertexPosition); 106 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); 107 | gl.vertexAttribPointer(gl.shaderProgram.aVertexPosition, 3, gl.FLOAT, false, 0, 0); 108 | 109 | //设置索引,但不用往shader中赋值 110 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); 111 | //绘图 112 | gl.drawElements(gl.TRIANGLES, this.indices.length, gl.UNSIGNED_SHORT, 0); 113 | 114 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 115 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 116 | gl.bindTexture(gl.TEXTURE_2D, null); 117 | } 118 | } 119 | }, 120 | 121 | //释放显存中的buffer资源 122 | releaseBuffers: function() { 123 | //释放显卡中的资源 124 | if (gl.isBuffer(this.vertexBuffer)) { 125 | gl.deleteBuffer(this.vertexBuffer); 126 | } 127 | if (gl.isBuffer(this.indexBuffer)) { 128 | gl.deleteBuffer(this.indexBuffer); 129 | } 130 | if (gl.isBuffer(this.textureCoordBuffer)) { 131 | gl.deleteBuffer(this.textureCoordBuffer); 132 | } 133 | this.vertexBuffer = null; 134 | this.indexBuffer = null; 135 | this.textureCoordBuffer = null; 136 | }, 137 | 138 | destroy: function() { 139 | this.parent = null; 140 | this.releaseBuffers(); 141 | if (this.material instanceof TextureMaterial) { 142 | this.material.releaseTexture(); 143 | this.material = null; 144 | } 145 | }, 146 | 147 | //需要子类重写 148 | getPosition: function() { 149 | var position = this.matrix.getColumnTrans(); 150 | return position; 151 | }, 152 | 153 | //需要子类重写 154 | setPosition: function(x, y, z) { 155 | this.matrix.setColumnTrans(x, y, z); 156 | }, 157 | 158 | worldTranslate: function(x, y, z) { 159 | this.matrix.worldTranslate(x, y, z); 160 | }, 161 | 162 | localTranslate: function(x, y, z) { 163 | this.matrix.localTranslate(x, y, z); 164 | }, 165 | 166 | worldScale: function(scaleX, scaleY, scaleZ) { 167 | this.matrix.worldScale(scaleX, scaleY, scaleZ); 168 | }, 169 | 170 | localScale: function(scaleX, scaleY, scaleZ) { 171 | this.matrix.localScale(scaleX, scaleY, scaleZ); 172 | }, 173 | 174 | worldRotateX: function(radian) { 175 | this.matrix.worldRotateX(radian); 176 | }, 177 | 178 | worldRotateY: function(radian) { 179 | this.matrix.worldRotateY(radian); 180 | }, 181 | 182 | worldRotateZ: function(radian) { 183 | this.matrix.worldRotateZ(radian); 184 | }, 185 | 186 | worldRotateByVector: function(radian, vector) { 187 | this.matrix.worldRotateByVector(radian, vector); 188 | }, 189 | 190 | localRotateX: function(radian) { 191 | this.matrix.localRotateX(radian); 192 | }, 193 | 194 | localRotateY: function(radian) { 195 | this.matrix.localRotateY(radian); 196 | }, 197 | 198 | localRotateZ: function(radian) { 199 | this.matrix.localRotateZ(radian); 200 | }, 201 | 202 | //localVector指的是相对于模型坐标系中的向量 203 | localRotateByVector: function(radian, localVector) { 204 | this.matrix.localRotateByVector(radian, localVector); 205 | }, 206 | 207 | getXAxisDirection: function() { 208 | var columnX = this.matrix.getColumnX(); //Vertice 209 | var directionX = columnX.getVector(); //Vector 210 | directionX.normalize(); 211 | return directionX; 212 | }, 213 | 214 | getYAxisDirection: function() { 215 | var columnY = this.matrix.getColumnY(); 216 | var directionY = columnY.getVector(); 217 | directionY.normalize(); 218 | return directionY; 219 | }, 220 | 221 | getZAxisDirection: function() { 222 | var columnZ = this.matrix.getColumnZ(); 223 | var directionZ = columnZ.getVector(); 224 | directionZ.normalize(); 225 | return directionZ; 226 | } 227 | }; 228 | return Object3D; 229 | }); -------------------------------------------------------------------------------- /js/world/Object3DComponents.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Matrix", "world/Object3D", "world/PerspectiveCamera"], 2 | function(Kernel, Matrix, Object3D, PerspectiveCamera) { 3 | //三维对象集合 4 | var Object3DComponents = function() { 5 | this.id = ++Kernel.idCounter; 6 | this.matrix = new Matrix(); 7 | this.visible = true; 8 | this.parent = null; 9 | this.children = []; 10 | }; 11 | Object3DComponents.prototype = { 12 | constructor: Object3DComponents, 13 | 14 | add: function(obj) { 15 | if (!(obj instanceof Object3D || obj instanceof Object3DComponents)) { 16 | throw "invalid obj: not World.Object3D or Object3DComponents"; 17 | } 18 | 19 | if (this.findObjById(obj.id) !== null) { 20 | console.debug("obj已经存在于Object3DComponents中,无法将其再次加入!"); 21 | return; 22 | } else { 23 | this.children.push(obj); 24 | obj.parent = this; 25 | } 26 | }, 27 | 28 | remove: function(obj) { 29 | if (obj) { 30 | var result = this.findObjById(obj.id); 31 | if (result === null) { 32 | console.debug("obj不存在于Object3DComponents中,所以无法将其从中删除!"); 33 | return false; 34 | } 35 | obj.destroy(); 36 | this.children.splice(result.index, 1); 37 | obj = null; 38 | return true; 39 | } else { 40 | return false; 41 | } 42 | }, 43 | 44 | //销毁所有的子节点 45 | clear: function() { 46 | for (var i = 0; i < this.children.length; i++) { 47 | var obj = this.children[i]; 48 | obj.destroy(); 49 | } 50 | this.children = []; 51 | }, 52 | 53 | //销毁自身及其子节点 54 | destroy: function() { 55 | this.parent = null; 56 | this.clear(); 57 | }, 58 | 59 | findObjById: function(objId) { 60 | for (var i = 0; i < this.children.length; i++) { 61 | var obj = this.children[i]; 62 | if (obj.id == objId) { 63 | obj.index = i; 64 | return obj; 65 | } 66 | } 67 | return null; 68 | }, 69 | 70 | draw: function(camera) { 71 | if (!(camera instanceof PerspectiveCamera)) { 72 | throw "invalid camera: not World.PerspectiveCamera"; 73 | } 74 | 75 | for (var i = 0; i < this.children.length; i++) { 76 | var obj = this.children[i]; 77 | if (obj) { 78 | if (obj.visible) { 79 | obj.draw(camera); 80 | } 81 | } 82 | } 83 | }, 84 | 85 | worldTranslate: function(x, y, z) { 86 | this.matrix.worldTranslate(x, y, z); 87 | }, 88 | 89 | localTranslate: function(x, y, z) { 90 | this.matrix.localTranslate(x, y, z); 91 | }, 92 | 93 | worldScale: function(scaleX, scaleY, scaleZ) { 94 | this.matrix.worldScale(scaleX, scaleY, scaleZ); 95 | }, 96 | 97 | localScale: function(scaleX, scaleY, scaleZ) { 98 | this.matrix.localScale(scaleX, scaleY, scaleZ); 99 | }, 100 | 101 | worldRotateX: function(radian) { 102 | this.matrix.worldRotateX(radian); 103 | }, 104 | 105 | worldRotateY: function(radian) { 106 | this.matrix.worldRotateY(radian); 107 | }, 108 | 109 | worldRotateZ: function(radian) { 110 | this.matrix.worldRotateZ(radian); 111 | }, 112 | 113 | worldRotateByVector: function(radian, vector) { 114 | this.matrix.worldRotateByVector(radian, vector); 115 | }, 116 | 117 | localRotateX: function(radian) { 118 | this.matrix.localRotateX(radian); 119 | }, 120 | 121 | localRotateY: function(radian) { 122 | this.matrix.localRotateY(radian); 123 | }, 124 | 125 | localRotateZ: function(radian) { 126 | this.matrix.localRotateZ(radian); 127 | }, 128 | 129 | //localVector指的是相对于模型坐标系中的向量 130 | localRotateByVector: function(radian, localVector) { 131 | this.matrix.localRotateByVector(radian, localVector); 132 | } 133 | }; 134 | return Object3DComponents; 135 | }); -------------------------------------------------------------------------------- /js/world/OsmTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/TiledLayer"], function(TiledLayer) { 2 | //OpenStreetMap 3 | var OsmTiledLayer = function(args) { 4 | TiledLayer.apply(this, arguments); 5 | }; 6 | OsmTiledLayer.prototype = new TiledLayer(); 7 | OsmTiledLayer.prototype.constructor = OsmTiledLayer; 8 | OsmTiledLayer.prototype.getImageUrl = function(level, row, column) { 9 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 10 | var sum = level + row + column; 11 | var idx = sum % 3; 12 | var server = ["a","b","c"][idx]; 13 | var url = "//"+server+".tile.openstreetmap.org/"+level+"/"+column+"/"+row+".png"; 14 | return url; 15 | }; 16 | return OsmTiledLayer; 17 | }); -------------------------------------------------------------------------------- /js/world/PerspectiveCamera.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Utils", "world/Math", "world/Vertice", "world/Vector", "world/Line", "world/TileGrid", "world/Matrix", "world/Object3D"], 2 | function(Kernel, Utils, MathUtils, Vertice, Vector, Line, TileGrid, Matrix, Object3D) { 3 | 4 | var PerspectiveCamera = function(fov, aspect, near, far) { 5 | fov = fov !== undefined ? fov : 90; 6 | aspect = aspect !== undefined ? aspect : 1; 7 | near = near !== undefined ? near : 1; 8 | far = far !== undefined ? far : 1; 9 | if (!Utils.isNumber(fov)) { 10 | throw "invalid fov: not number"; 11 | } 12 | if (!Utils.isNumber(aspect)) { 13 | throw "invalid aspect: not number"; 14 | } 15 | if (!Utils.isNumber(near)) { 16 | throw "invalid near: not number"; 17 | } 18 | if (!Utils.isNumber(far)) { 19 | throw "invalid far: not number"; 20 | } 21 | this.fov = fov; 22 | this.aspect = aspect; 23 | this.near = near; 24 | this.far = far; 25 | Object3D.apply(this, null); 26 | this.pitch = 90; 27 | this.projMatrix = new Matrix(); 28 | this.setPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); 29 | }; 30 | PerspectiveCamera.prototype = new Object3D(); 31 | PerspectiveCamera.prototype.constructor = PerspectiveCamera; 32 | PerspectiveCamera.prototype.Enum = { 33 | EARTH_FULL_OVERSPREAD_SCREEN: "EARTH_FULL_OVERSPREAD_SCREEN", //Canvas内全部被地球充满 34 | EARTH_NOT_FULL_OVERSPREAD_SCREEN: "EARTH_NOT_FULL_OVERSPREAD_SCREEN" //Canvas没有全部被地球充满 35 | }; 36 | PerspectiveCamera.prototype.setPerspectiveMatrix = function(fov, aspect, near, far) { 37 | fov = fov !== undefined ? fov : 90; 38 | aspect = aspect !== undefined ? aspect : 1; 39 | near = near !== undefined ? near : 1; 40 | far = far !== undefined ? far : 1; 41 | if (!Utils.isNumber(fov)) { 42 | throw "invalid fov: not number"; 43 | } 44 | if (!Utils.isNumber(aspect)) { 45 | throw "invalid aspect: not number"; 46 | } 47 | if (!Utils.isNumber(near)) { 48 | throw "invalid near: not number"; 49 | } 50 | if (!Utils.isNumber(far)) { 51 | throw "invalid far: not number"; 52 | } 53 | this.fov = fov; 54 | this.aspect = aspect; 55 | this.near = near; 56 | this.far = far; 57 | var mat = [1, 0, 0, 0, 58 | 0, 1, 0, 0, 59 | 0, 0, 1, 0, 60 | 0, 0, 0, 1 61 | ]; 62 | var halfFov = this.fov * Math.PI / 180 / 2; 63 | var a = 1 / Math.tan(halfFov); 64 | var b = this.far - this.near; 65 | 66 | mat[0] = a / this.aspect; 67 | mat[5] = a; 68 | mat[10] = -(this.far + this.near) / b; 69 | mat[11] = -1; 70 | mat[14] = -2 * this.near * this.far / b; 71 | mat[15] = 0; 72 | 73 | //by comparision with matrixProjection.exe and glMatrix, 74 | //the 11th element is always -1 75 | this.projMatrix.setElements(mat[0], mat[1], mat[2], mat[3], 76 | mat[4], mat[5], mat[6], mat[7], 77 | mat[8], mat[9], mat[10], mat[11], 78 | mat[12], mat[13], mat[14], mat[15]); 79 | }; 80 | 81 | PerspectiveCamera.prototype.getLightDirection = function() { 82 | var dirVertice = this.matrix.getColumnZ(); 83 | var direction = new Vector(-dirVertice.x, -dirVertice.y, -dirVertice.z); 84 | direction.normalize(); 85 | return direction; 86 | }; 87 | 88 | //获取投影矩阵与视点矩阵的乘积 89 | PerspectiveCamera.prototype.getProjViewMatrix = function() { 90 | var viewMatrix = this.getViewMatrix(); 91 | var projViewMatrix = this.projMatrix.multiplyMatrix(viewMatrix); 92 | return projViewMatrix; 93 | }; 94 | 95 | PerspectiveCamera.prototype.setFov = function(fov) { 96 | if (!Utils.isPositive(fov)) { 97 | throw "invalid fov"; 98 | } 99 | this.setPerspectiveMatrix(fov, this.aspect, this.near, this.far); 100 | }; 101 | 102 | PerspectiveCamera.prototype.setAspect = function(aspect) { 103 | if (!Utils.isPositive(aspect)) { 104 | throw "invalid aspect"; 105 | } 106 | this.setPerspectiveMatrix(this.fov, aspect, this.near, this.far); 107 | }; 108 | 109 | PerspectiveCamera.prototype.setNear = function(near) { 110 | if (!Utils.isPositive(near)) { 111 | throw "invalid near"; 112 | } 113 | this.setPerspectiveMatrix(this.fov, this.aspect, near, this.far); 114 | }; 115 | 116 | PerspectiveCamera.prototype.setFar = function(far) { 117 | if (!Utils.isPositive(far)) { 118 | throw "invalid far"; 119 | } 120 | this.setPerspectiveMatrix(this.fov, this.aspect, this.near, far); 121 | }; 122 | 123 | PerspectiveCamera.prototype.getViewMatrix = function() { 124 | //视点矩阵是camera的模型矩阵的逆矩阵 125 | return this.matrix.getInverseMatrix(); 126 | }; 127 | 128 | PerspectiveCamera.prototype.look = function(cameraPnt, targetPnt, upDirection) { 129 | if (!(cameraPnt instanceof Vertice)) { 130 | throw "invalid cameraPnt: not Vertice"; 131 | } 132 | if (!(targetPnt instanceof Vertice)) { 133 | throw "invalid targetPnt: not Vertice"; 134 | } 135 | upDirection = upDirection !== undefined ? upDirection : new Vector(0, 1, 0); 136 | if (!(upDirection instanceof Vector)) { 137 | throw "invalid upDirection: not Vector"; 138 | } 139 | var cameraPntCopy = cameraPnt.getCopy(); 140 | var targetPntCopy = targetPnt.getCopy(); 141 | var up = upDirection.getCopy(); 142 | var transX = cameraPntCopy.x; 143 | var transY = cameraPntCopy.y; 144 | var transZ = cameraPntCopy.z; 145 | var zAxis = new Vector(cameraPntCopy.x - targetPntCopy.x, cameraPntCopy.y - targetPntCopy.y, cameraPntCopy.z - targetPntCopy.z).normalize(); 146 | var xAxis = up.cross(zAxis).normalize(); 147 | var yAxis = zAxis.cross(xAxis).normalize(); 148 | 149 | this.matrix.setColumnX(xAxis.x, xAxis.y, xAxis.z); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置X轴方向 150 | this.matrix.setColumnY(yAxis.x, yAxis.y, yAxis.z); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置Y轴方向 151 | this.matrix.setColumnZ(zAxis.x, zAxis.y, zAxis.z); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置Z轴方向 152 | this.matrix.setColumnTrans(transX, transY, transZ); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置偏移量 153 | this.matrix.setLastRowDefault(); 154 | 155 | var deltaX = cameraPntCopy.x - targetPntCopy.x; 156 | var deltaY = cameraPntCopy.y - targetPntCopy.y; 157 | var deltaZ = cameraPntCopy.z - targetPntCopy.z; 158 | var far = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ); 159 | this.setFar(far); 160 | }; 161 | 162 | PerspectiveCamera.prototype.lookAt = function(targetPnt, upDirection) { 163 | if (!(targetPnt instanceof Vertice)) { 164 | throw "invalid targetPnt: not Vertice"; 165 | } 166 | upDirection = upDirection !== undefined ? upDirection : new Vector(0, 1, 0); 167 | if (!(upDirection instanceof Vector)) { 168 | throw "invalid upDirection: not Vector"; 169 | } 170 | var targetPntCopy = targetPnt.getCopy(); 171 | var upDirectionCopy = upDirection.getCopy(); 172 | var position = this.getPosition(); 173 | this.look(position, targetPntCopy, upDirectionCopy); 174 | }; 175 | 176 | //点变换: World->NDC 177 | PerspectiveCamera.prototype.convertVerticeFromWorldToNDC = function(verticeInWorld, /*optional*/ projViewMatrix) { 178 | if (!(verticeInWorld instanceof Vertice)) { 179 | throw "invalid verticeInWorld: not Vertice"; 180 | } 181 | if (!(projViewMatrix instanceof Matrix)) { 182 | projViewMatrix = this.getProjViewMatrix(); 183 | } 184 | var columnWorld = [verticeInWorld.x, verticeInWorld.y, verticeInWorld.z, 1]; 185 | var columnProject = projViewMatrix.multiplyColumn(columnWorld); 186 | var w = columnProject[3]; 187 | var columnNDC = []; 188 | columnNDC[0] = columnProject[0] / w; 189 | columnNDC[1] = columnProject[1] / w; 190 | columnNDC[2] = columnProject[2] / w; 191 | columnNDC[3] = 1; 192 | var verticeInNDC = new Vertice(columnNDC[0], columnNDC[1], columnNDC[2]); 193 | return verticeInNDC; 194 | }; 195 | 196 | //点变换: NDC->World 197 | PerspectiveCamera.prototype.convertVerticeFromNdcToWorld = function(verticeInNDC) { 198 | if (!(verticeInNDC instanceof Vertice)) { 199 | throw "invalid verticeInNDC: not Vertice"; 200 | } 201 | var columnNDC = [verticeInNDC.x, verticeInNDC.y, verticeInNDC.z, 1]; //NDC归一化坐标 202 | var inverseProj = this.projMatrix.getInverseMatrix(); //投影矩阵的逆矩阵 203 | var columnCameraTemp = inverseProj.multiplyColumn(columnNDC); //带引号的“视坐标” 204 | var cameraX = columnCameraTemp[0] / columnCameraTemp[3]; 205 | var cameraY = columnCameraTemp[1] / columnCameraTemp[3]; 206 | var cameraZ = columnCameraTemp[2] / columnCameraTemp[3]; 207 | var cameraW = 1; 208 | var columnCamera = [cameraX, cameraY, cameraZ, cameraW]; //真实的视坐标 209 | 210 | var viewMatrix = this.getViewMatrix(); 211 | var inverseView = viewMatrix.getInverseMatrix(); //视点矩阵的逆矩阵 212 | var columnWorld = inverseView.multiplyColumn(columnCamera); //单击点的世界坐标 213 | var verticeInWorld = new Vertice(columnWorld[0], columnWorld[1], columnWorld[2]); 214 | return verticeInWorld; 215 | }; 216 | 217 | //点变换: Camera->World 218 | PerspectiveCamera.prototype.convertVerticeFromCameraToWorld = function(verticeInCamera, /*optional*/ viewMatrix) { 219 | if (!(verticeInCamera instanceof Vertice)) { 220 | throw "invalid verticeInCamera: not Vertice"; 221 | } 222 | if (!(viewMatrix instanceof Matrix)) { 223 | viewMatrix = this.getViewMatrix(); 224 | } 225 | var verticeInCameraCopy = verticeInCamera.getCopy(); 226 | var inverseMatrix = viewMatrix.getInverseMatrix(); 227 | var column = [verticeInCameraCopy.x, verticeInCameraCopy.y, verticeInCameraCopy.z, 1]; 228 | var column2 = inverseMatrix.multiplyColumn(column); 229 | var verticeInWorld = new Vertice(column2[0], column2[1], column2[2]); 230 | return verticeInWorld; 231 | }; 232 | 233 | //向量变换: Camera->World 234 | PerspectiveCamera.prototype.convertVectorFromCameraToWorld = function(vectorInCamera, /*optional*/ viewMatrix) { 235 | if (!(vectorInCamera instanceof Vector)) { 236 | throw "invalid vectorInCamera: not Vector"; 237 | } 238 | if (!(viewMatrix instanceof Matrix)) { 239 | viewMatrix = this.getViewMatrix(); 240 | } 241 | var vectorInCameraCopy = vectorInCamera.getCopy(); 242 | var verticeInCamera = vectorInCameraCopy.getVertice(); 243 | var verticeInWorld = this.convertVerticeFromCameraToWorld(verticeInCamera, viewMatrix); 244 | var originInWorld = this.getPosition(); 245 | var vectorInWorld = verticeInWorld.minus(originInWorld); 246 | vectorInWorld.normalize(); 247 | return vectorInWorld; 248 | }; 249 | 250 | //根据canvasX和canvasY获取拾取向量 251 | PerspectiveCamera.prototype.getPickDirectionByCanvas = function(canvasX, canvasY) { 252 | if (!Utils.isNumber(canvasX)) { 253 | throw "invalid canvasX: not number"; 254 | } 255 | if (!Utils.isNumber(canvasY)) { 256 | throw "invalid canvasY: not number"; 257 | } 258 | var ndcXY = MathUtils.convertPointFromCanvasToNDC(canvasX, canvasY); 259 | var pickDirection = this.getPickDirectionByNDC(ndcXY[0], ndcXY[1]); 260 | return pickDirection; 261 | }; 262 | 263 | //获取当前视线与地球的交点 264 | PerspectiveCamera.prototype.getDirectionIntersectPointWithEarth = function() { 265 | var dir = this.getLightDirection(); 266 | var p = this.getPosition(); 267 | var line = new Line(p, dir); 268 | var result = this.getPickCartesianCoordInEarthByLine(line); 269 | return result; 270 | }; 271 | 272 | //根据ndcX和ndcY获取拾取向量 273 | PerspectiveCamera.prototype.getPickDirectionByNDC = function(ndcX, ndcY) { 274 | if (!Utils.isNumber(ndcX)) { 275 | throw "invalid ndcX: not number"; 276 | } 277 | if (!Utils.isNumber(ndcY)) { 278 | throw "invalid ndcY: not number"; 279 | } 280 | var verticeInNDC = new Vertice(ndcX, ndcY, 0.499); 281 | var verticeInWorld = this.convertVerticeFromNdcToWorld(verticeInNDC); 282 | var cameraPositon = this.getPosition(); //摄像机的世界坐标 283 | var pickDirection = verticeInWorld.minus(cameraPositon); 284 | pickDirection.normalize(); 285 | return pickDirection; 286 | }; 287 | 288 | //获取直线与地球的交点,该方法与World.Math.getLineIntersectPointWithEarth功能基本一样,只不过该方法对相交点进行了远近排序 289 | PerspectiveCamera.prototype.getPickCartesianCoordInEarthByLine = function(line) { 290 | if (!(line instanceof Line)) { 291 | throw "invalid line: not Line"; 292 | } 293 | var result = []; 294 | //pickVertice是笛卡尔空间直角坐标系中的坐标 295 | var pickVertices = MathUtils.getLineIntersectPointWithEarth(line); 296 | if (pickVertices.length === 0) { 297 | //没有交点 298 | result = []; 299 | } else if (pickVertices.length == 1) { 300 | //一个交点 301 | result = pickVertices; 302 | } else if (pickVertices.length == 2) { 303 | //两个交点 304 | var pickVerticeA = pickVertices[0]; 305 | var pickVerticeB = pickVertices[1]; 306 | var cameraVertice = this.getPosition(); 307 | var lengthA = MathUtils.getLengthFromVerticeToVertice(cameraVertice, pickVerticeA); 308 | var lengthB = MathUtils.getLengthFromVerticeToVertice(cameraVertice, pickVerticeB); 309 | //将距离人眼更近的那个点放到前面 310 | result = lengthA <= lengthB ? [pickVerticeA, pickVerticeB] : [pickVerticeB, pickVerticeA]; 311 | } 312 | return result; 313 | }; 314 | 315 | //计算拾取射线与地球的交点,以笛卡尔空间直角坐标系坐标组的组的形式返回 316 | PerspectiveCamera.prototype.getPickCartesianCoordInEarthByCanvas = function(canvasX, canvasY, options) { 317 | if (!Utils.isNumber(canvasX)) { 318 | throw "invalid canvasX: not number"; 319 | } 320 | if (!Utils.isNumber(canvasY)) { 321 | throw "invalid canvasY: not number"; 322 | } 323 | var pickDirection = this.getPickDirectionByCanvas(canvasX, canvasY); 324 | var p = this.getPosition(); 325 | var line = new Line(p, pickDirection); 326 | var result = this.getPickCartesianCoordInEarthByLine(line); 327 | return result; 328 | }; 329 | 330 | PerspectiveCamera.prototype.getPickCartesianCoordInEarthByNDC = function(ndcX, ndcY) { 331 | if (!Utils.isNumber(ndcX)) { 332 | throw "invalid ndcX: not number"; 333 | } 334 | if (!Utils.isNumber(ndcY)) { 335 | throw "invalid ndcY: not number"; 336 | } 337 | var pickDirection = this.getPickDirectionByNDC(ndcX, ndcY); 338 | var p = this.getPosition(); 339 | var line = new Line(p, pickDirection); 340 | var result = this.getPickCartesianCoordInEarthByLine(line); 341 | return result; 342 | }; 343 | 344 | //得到摄像机的XOZ平面的方程 345 | PerspectiveCamera.prototype.getPlanXOZ = function() { 346 | var position = this.getPosition(); 347 | var direction = this.getLightDirection(); 348 | var plan = MathUtils.getCrossPlaneByLine(position, direction); 349 | return plan; 350 | }; 351 | 352 | //设置观察到的层级 353 | PerspectiveCamera.prototype.setLevel = function(level) { 354 | if (!Utils.isInteger(level)) { 355 | throw "invalid level"; 356 | } 357 | if (level < 0) { 358 | return; 359 | } 360 | var pOld = this.getPosition(); 361 | if (pOld.x === 0 && pOld.y === 0 && pOld.z === 0) { 362 | //初始设置camera 363 | var length = MathUtils.getLengthFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 364 | var origin = new Vertice(0, 0, 0); 365 | var vector = this.getLightDirection().getOpposite(); 366 | vector.setLength(length); 367 | var newPosition = vector.getVertice(); 368 | this.look(newPosition, origin); 369 | } else { 370 | var length2SurfaceNow = MathUtils.getLengthFromCamera2EarthSurface(Kernel.globe.CURRENT_LEVEL); 371 | var length2Surface = MathUtils.getLengthFromCamera2EarthSurface(level); 372 | var deltaLength = length2SurfaceNow - length2Surface; 373 | var dir = this.getLightDirection(); 374 | dir.setLength(deltaLength); 375 | var pNew = pOld.plus(dir); 376 | this.setPosition(pNew.x, pNew.y, pNew.z); 377 | } 378 | Kernel.globe.CURRENT_LEVEL = level; 379 | }; 380 | 381 | //判断世界坐标系中的点是否在Canvas中可见 382 | //options:projView、verticeInNDC 383 | PerspectiveCamera.prototype.isWorldVerticeVisibleInCanvas = function(verticeInWorld, options) { 384 | if (!(verticeInWorld instanceof Vertice)) { 385 | throw "invalid verticeInWorld: not Vertice"; 386 | } 387 | options = options || {}; 388 | var threshold = typeof options.threshold == "number" ? Math.abs(options.threshold) : 1; 389 | var cameraP = this.getPosition(); 390 | var dir = verticeInWorld.minus(cameraP); 391 | var line = new Line(cameraP, dir); 392 | var pickResult = this.getPickCartesianCoordInEarthByLine(line); 393 | if (pickResult.length > 0) { 394 | var pickVertice = pickResult[0]; 395 | var length2Vertice = MathUtils.getLengthFromVerticeToVertice(cameraP, verticeInWorld); 396 | var length2Pick = MathUtils.getLengthFromVerticeToVertice(cameraP, pickVertice); 397 | if (length2Vertice < length2Pick + 5) { 398 | if (!(options.verticeInNDC instanceof Vertice)) { 399 | if (!(options.projView instanceof Matrix)) { 400 | options.projView = this.getProjViewMatrix(); 401 | } 402 | options.verticeInNDC = this.convertVerticeFromWorldToNDC(verticeInWorld, options.projView); 403 | } 404 | var result = options.verticeInNDC.x >= -1 && options.verticeInNDC.x <= 1 && options.verticeInNDC.y >= -threshold && options.verticeInNDC.y <= 1; 405 | return result; 406 | } 407 | } 408 | return false; 409 | }; 410 | 411 | //判断地球表面的某个经纬度在Canvas中是否应该可见 412 | //options:projView、verticeInNDC 413 | PerspectiveCamera.prototype.isGeoVisibleInCanvas = function(lon, lat, options) { 414 | var verticeInWorld = MathUtils.geographicToCartesianCoord(lon, lat); 415 | var result = this.isWorldVerticeVisibleInCanvas(verticeInWorld, options); 416 | return result; 417 | }; 418 | 419 | 420 | /** 421 | * 算法,一个切片需要渲染需要满足如下三个条件: 422 | * 1.至少要有一个点在Canvas中可见 423 | * 2.NDC面积足够大 424 | * 3.形成的NDC四边形是顺时针方向 425 | */ 426 | //获取level层级下的可见切片 427 | //options:projView 428 | PerspectiveCamera.prototype.getVisibleTilesByLevel = function(level, options) { 429 | if (!Utils.isNonNegativeInteger(level)) { 430 | throw "invalid level"; 431 | } 432 | var result = []; 433 | options = options || {}; 434 | if (!(options.projView instanceof Matrix)) { 435 | options.projView = this.getProjViewMatrix(); 436 | } 437 | //向左、向右、向上、向下最大的循环次数 438 | var LOOP_LIMIT = Math.min(10, Math.pow(2, level) - 1); 439 | 440 | var mathOptions = { 441 | maxSize: Math.pow(2, level) 442 | }; 443 | 444 | function checkVisible(visibleInfo) { 445 | if (visibleInfo.area >= 5000 && visibleInfo.clockwise) { 446 | if (visibleInfo.visibleCount >= 1) { 447 | return true; 448 | } 449 | } 450 | return false; 451 | } 452 | 453 | function handleRow(centerRow, centerColumn) { 454 | var result = []; 455 | var grid = new TileGrid(level, centerRow, centerColumn); // {level:level,row:centerRow,column:centerColumn}; 456 | var visibleInfo = this.getTileVisibleInfo(grid.level, grid.row, grid.column, options); 457 | var isRowCenterVisible = checkVisible(visibleInfo); 458 | if (isRowCenterVisible) { 459 | grid.visibleInfo = visibleInfo; 460 | result.push(grid); 461 | 462 | //向左遍历至不可见 463 | var leftLoopTime = 0; //向左循环的次数 464 | var leftColumn = centerColumn; 465 | var visible; 466 | while (leftLoopTime < LOOP_LIMIT) { 467 | leftLoopTime++; 468 | grid = MathUtils.getTileGridByBrother(level, centerRow, leftColumn, MathUtils.LEFT, mathOptions); 469 | leftColumn = grid.column; 470 | visibleInfo = this.getTileVisibleInfo(grid.level, grid.row, grid.column, options); 471 | visible = checkVisible(visibleInfo); 472 | if (visible) { 473 | grid.visibleInfo = visibleInfo; 474 | result.push(grid); 475 | } else { 476 | break; 477 | } 478 | } 479 | 480 | //向右遍历至不可见 481 | var rightLoopTime = 0; //向右循环的次数 482 | var rightColumn = centerColumn; 483 | while (rightLoopTime < LOOP_LIMIT) { 484 | rightLoopTime++; 485 | grid = MathUtils.getTileGridByBrother(level, centerRow, rightColumn, MathUtils.RIGHT, mathOptions); 486 | rightColumn = grid.column; 487 | visibleInfo = this.getTileVisibleInfo(grid.level, grid.row, grid.column, options); 488 | visible = checkVisible(visibleInfo); 489 | if (visible) { 490 | grid.visibleInfo = visibleInfo; 491 | result.push(grid); 492 | } else { 493 | break; 494 | } 495 | } 496 | } 497 | return result; 498 | } 499 | 500 | var verticalCenterInfo = this._getVerticalVisibleCenterInfo(options); 501 | var centerGrid = MathUtils.getTileGridByGeo(verticalCenterInfo.lon, verticalCenterInfo.lat, level); 502 | var handleRowThis = handleRow.bind(this); 503 | 504 | var rowResult = handleRowThis(centerGrid.row, centerGrid.column); 505 | result = result.concat(rowResult); 506 | 507 | //循环向下处理至不可见 508 | var bottomLoopTime = 0; //向下循环的次数 509 | var bottomRow = centerGrid.row; 510 | while (bottomLoopTime < LOOP_LIMIT) { 511 | bottomLoopTime++; 512 | grid = MathUtils.getTileGridByBrother(level, bottomRow, centerGrid.column, MathUtils.BOTTOM, mathOptions); 513 | bottomRow = grid.row; 514 | rowResult = handleRowThis(grid.row, grid.column); 515 | if (rowResult.length > 0) { 516 | result = result.concat(rowResult); 517 | } else { 518 | //已经向下循环到不可见,停止向下循环 519 | break; 520 | } 521 | } 522 | 523 | //循环向上处理至不可见 524 | var topLoopTime = 0; //向上循环的次数 525 | var topRow = centerGrid.row; 526 | while (topLoopTime < LOOP_LIMIT) { 527 | topLoopTime++; 528 | grid = MathUtils.getTileGridByBrother(level, topRow, centerGrid.column, MathUtils.TOP, mathOptions); 529 | topRow = grid.row; 530 | rowResult = handleRowThis(grid.row, grid.column); 531 | if (rowResult.length > 0) { 532 | result = result.concat(rowResult); 533 | } else { 534 | //已经向上循环到不可见,停止向上循环 535 | break; 536 | } 537 | } 538 | 539 | return result; 540 | }; 541 | 542 | //options:projView 543 | PerspectiveCamera.prototype.getTileVisibleInfo = function(level, row, column, options) { 544 | if (!Utils.isNonNegativeInteger(level)) { 545 | throw "invalid level"; 546 | } 547 | if (!Utils.isNonNegativeInteger(row)) { 548 | throw "invalid row"; 549 | } 550 | if (!Utils.isNonNegativeInteger(column)) { 551 | throw "invalid column"; 552 | } 553 | options = options || {}; 554 | var threshold = typeof options.threshold == "number" ? Math.abs(options.threshold) : 1; 555 | var result = { 556 | lb: { 557 | lon: null, 558 | lat: null, 559 | verticeInWorld: null, 560 | verticeInNDC: null, 561 | visible: false 562 | }, 563 | lt: { 564 | lon: null, 565 | lat: null, 566 | verticeInWorld: null, 567 | verticeInNDC: null, 568 | visible: false 569 | }, 570 | rt: { 571 | lon: null, 572 | lat: null, 573 | verticeInWorld: null, 574 | verticeInNDC: null, 575 | visible: false 576 | }, 577 | rb: { 578 | lon: null, 579 | lat: null, 580 | verticeInWorld: null, 581 | verticeInNDC: null, 582 | visible: false 583 | }, 584 | Egeo: null, 585 | visibleCount: 0, 586 | clockwise: false, 587 | width: null, 588 | height: null, 589 | area: null 590 | }; 591 | if (!(options.projView instanceof Matrix)) { 592 | options.projView = this.getProjViewMatrix(); 593 | } 594 | result.Egeo = MathUtils.getTileGeographicEnvelopByGrid(level, row, column); 595 | var tileMinLon = result.Egeo.minLon; 596 | var tileMaxLon = result.Egeo.maxLon; 597 | var tileMinLat = result.Egeo.minLat; 598 | var tileMaxLat = result.Egeo.maxLat; 599 | 600 | //左下角 601 | result.lb.lon = tileMinLon; 602 | result.lb.lat = tileMinLat; 603 | result.lb.verticeInWorld = MathUtils.geographicToCartesianCoord(result.lb.lon, result.lb.lat); 604 | result.lb.verticeInNDC = this.convertVerticeFromWorldToNDC(result.lb.verticeInWorld, options.projView); 605 | result.lb.visible = this.isWorldVerticeVisibleInCanvas(result.lb.verticeInWorld, { 606 | verticeInNDC: result.lb.verticeInNDC, 607 | projView: options.projView, 608 | threshold: threshold 609 | }); 610 | if (result.lb.visible) { 611 | result.visibleCount++; 612 | } 613 | 614 | //左上角 615 | result.lt.lon = tileMinLon; 616 | result.lt.lat = tileMaxLat; 617 | result.lt.verticeInWorld = MathUtils.geographicToCartesianCoord(result.lt.lon, result.lt.lat); 618 | result.lt.verticeInNDC = this.convertVerticeFromWorldToNDC(result.lt.verticeInWorld, options.projView); 619 | result.lt.visible = this.isWorldVerticeVisibleInCanvas(result.lt.verticeInWorld, { 620 | verticeInNDC: result.lt.verticeInNDC, 621 | projView: options.projView, 622 | threshold: threshold 623 | }); 624 | if (result.lt.visible) { 625 | result.visibleCount++; 626 | } 627 | 628 | //右上角 629 | result.rt.lon = tileMaxLon; 630 | result.rt.lat = tileMaxLat; 631 | result.rt.verticeInWorld = MathUtils.geographicToCartesianCoord(result.rt.lon, result.rt.lat); 632 | result.rt.verticeInNDC = this.convertVerticeFromWorldToNDC(result.rt.verticeInWorld, options.projView); 633 | result.rt.visible = this.isWorldVerticeVisibleInCanvas(result.rt.verticeInWorld, { 634 | verticeInNDC: result.rt.verticeInNDC, 635 | projView: options.projView, 636 | threshold: threshold 637 | }); 638 | if (result.rt.visible) { 639 | result.visibleCount++; 640 | } 641 | 642 | //右下角 643 | result.rb.lon = tileMaxLon; 644 | result.rb.lat = tileMinLat; 645 | result.rb.verticeInWorld = MathUtils.geographicToCartesianCoord(result.rb.lon, result.rb.lat); 646 | result.rb.verticeInNDC = this.convertVerticeFromWorldToNDC(result.rb.verticeInWorld, options.projView); 647 | result.rb.visible = this.isWorldVerticeVisibleInCanvas(result.rb.verticeInWorld, { 648 | verticeInNDC: result.rb.verticeInNDC, 649 | projView: options.projView, 650 | threshold: threshold 651 | }); 652 | if (result.rb.visible) { 653 | result.visibleCount++; 654 | } 655 | 656 | var ndcs = [result.lb.verticeInNDC, result.lt.verticeInNDC, result.rt.verticeInNDC, result.rb.verticeInNDC]; 657 | //计算方向 658 | var vector03 = ndcs[3].minus(ndcs[0]); 659 | vector03.z = 0; 660 | var vector01 = ndcs[1].minus(ndcs[0]); 661 | vector01.z = 0; 662 | var cross = vector03.cross(vector01); 663 | result.clockwise = cross.z > 0; 664 | //计算面积 665 | var topWidth = Math.sqrt(Math.pow(ndcs[1].x - ndcs[2].x, 2) + Math.pow(ndcs[1].y - ndcs[2].y, 2)) * Kernel.canvas.width / 2; 666 | var bottomWidth = Math.sqrt(Math.pow(ndcs[0].x - ndcs[3].x, 2) + Math.pow(ndcs[0].y - ndcs[3].y, 2)) * Kernel.canvas.width / 2; 667 | result.width = Math.floor((topWidth + bottomWidth) / 2); 668 | var leftHeight = Math.sqrt(Math.pow(ndcs[0].x - ndcs[1].x, 2) + Math.pow(ndcs[0].y - ndcs[1].y, 2)) * Kernel.canvas.height / 2; 669 | var rightHeight = Math.sqrt(Math.pow(ndcs[2].x - ndcs[3].x, 2) + Math.pow(ndcs[2].y - ndcs[3].y, 2)) * Kernel.canvas.height / 2; 670 | result.height = Math.floor((leftHeight + rightHeight) / 2); 671 | result.area = result.width * result.height; 672 | 673 | return result; 674 | }; 675 | 676 | //地球一直是关于纵轴中心对称的,获取垂直方向上中心点信息 677 | PerspectiveCamera.prototype._getVerticalVisibleCenterInfo = function(options) { 678 | options = options || {}; 679 | if (!options.projView) { 680 | options.projView = this.getProjViewMatrix(); 681 | } 682 | var result = { 683 | ndcY: null, 684 | pIntersect: null, 685 | lon: null, 686 | lat: null 687 | }; 688 | var pickResults; 689 | if (this.pitch == 90) { 690 | result.ndcY = 0; 691 | } else { 692 | var count = 10; 693 | var delta = 2.0 / count; 694 | var topNdcY = 1; 695 | var bottomNdcY = -1; 696 | var ndcY; 697 | //从上往下找topNdcY 698 | for (ndcY = 1.0; ndcY >= -1.0; ndcY -= delta) { 699 | pickResults = this.getPickCartesianCoordInEarthByNDC(0, ndcY); 700 | if (pickResults.length > 0) { 701 | topNdcY = ndcY; 702 | break; 703 | } 704 | } 705 | 706 | //从下往上找 707 | for (ndcY = -1.0; ndcY <= 1.0; ndcY += delta) { 708 | pickResults = this.getPickCartesianCoordInEarthByNDC(0, ndcY); 709 | if (pickResults.length > 0) { 710 | bottomNdcY = ndcY; 711 | break; 712 | } 713 | } 714 | result.ndcY = (topNdcY + bottomNdcY) / 2; 715 | } 716 | pickResults = this.getPickCartesianCoordInEarthByNDC(0, result.ndcY); 717 | result.pIntersect = pickResults[0]; 718 | var lonlat = MathUtils.cartesianCoordToGeographic(result.pIntersect); 719 | result.lon = lonlat[0]; 720 | result.lat = lonlat[1]; 721 | return result; 722 | }; 723 | 724 | return PerspectiveCamera; 725 | }); -------------------------------------------------------------------------------- /js/world/Plan.js: -------------------------------------------------------------------------------- 1 | define(["world/Utils"], function(Utils) { 2 | /** 3 | * 三维空间中的平面,其法向量为(A,B,C) 4 | * @param A 5 | * @param B 6 | * @param C 7 | * @param D 8 | * @return {Object} 9 | * @constructor 10 | */ 11 | var Plan = function(A, B, C, D) { 12 | if (!Utils.isNumber(A)) { 13 | throw "invalid A"; 14 | } 15 | if (!Utils.isNumber(B)) { 16 | throw "invalid B"; 17 | } 18 | if (!Utils.isNumber(C)) { 19 | throw "invalid C"; 20 | } 21 | if (!Utils.isNumber(D)) { 22 | throw "invalid D"; 23 | } 24 | this.A = A; 25 | this.B = B; 26 | this.C = C; 27 | this.D = D; 28 | }; 29 | Plan.prototype.constructor = Plan; 30 | Plan.prototype.getCopy = function() { 31 | var planCopy = new Plan(this.A, this.B, this.C, this.D); 32 | return planCopy; 33 | }; 34 | return Plan; 35 | }); -------------------------------------------------------------------------------- /js/world/Ray.js: -------------------------------------------------------------------------------- 1 | define(["world/Vertice", "world/Vector"], function(Vertice, Vector) { 2 | /** 3 | * 射线 4 | * @param position 射线起点 World.Vertice类型 5 | * @param direction 射线方向 World.Vector类型 6 | * @constructor 7 | */ 8 | var Ray = function(position, direction) { 9 | if (!(position instanceof Vertice)) { 10 | throw "invalid position"; 11 | } 12 | if (!(direction instanceof Vector)) { 13 | throw "invalid direction"; 14 | } 15 | this.vertice = position.getCopy(); 16 | this.vector = direction.getCopy(); 17 | this.vector.normalize(); 18 | }; 19 | Ray.prototype.constructor = Ray; 20 | Ray.prototype.setVertice = function(position) { 21 | if (!(position instanceof Vertice)) { 22 | throw "invalid position"; 23 | } 24 | this.vertice = position.getCopy(); 25 | return this; 26 | }; 27 | Ray.prototype.setVector = function(direction) { 28 | if (!(direction instanceof Vector)) { 29 | throw "invalid direction"; 30 | } 31 | this.vector = direction.getCopy(); 32 | this.vector.normalize(); 33 | return this; 34 | }; 35 | Ray.prototype.getCopy = function() { 36 | var rayCopy = new Ray(this.vertice, this.vector); 37 | return rayCopy; 38 | }; 39 | Ray.prototype.rotateVertice = function(vertice) { 40 | }; 41 | 42 | return Ray; 43 | }); -------------------------------------------------------------------------------- /js/world/Scene.js: -------------------------------------------------------------------------------- 1 | define(["world/Object3DComponents"], function(Object3DComponents) { 2 | var Scene = function(args) { 3 | Object3DComponents.apply(this, arguments); 4 | }; 5 | Scene.prototype = new Object3DComponents(); 6 | Scene.prototype.constructor = Scene; 7 | return Scene; 8 | }); -------------------------------------------------------------------------------- /js/world/ShaderContent.js: -------------------------------------------------------------------------------- 1 | define({ 2 | SIMPLE_SHADER: { 3 | VS_CONTENT: "attribute vec3 aVertexPosition;\nattribute vec2 aTextureCoord;\nvarying vec2 vTextureCoord;\nuniform mat4 uMVMatrix;\nuniform mat4 uPMatrix;\nvoid main()\n{\ngl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition,1.0);\nvTextureCoord = aTextureCoord;\n}", 4 | FS_CONTENT: "#ifdef GL_ES\nprecision highp float;\n#endif\nuniform bool uUseTexture;\nuniform float uShininess;\nuniform vec3 uLightDirection;\nuniform vec4 uLightAmbient;\nuniform vec4 uLightDiffuse;\nuniform vec4 uLightSpecular;\nvarying vec2 vTextureCoord;\nuniform sampler2D uSampler;\nvoid main()\n{\ngl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));\n}" 5 | } 6 | }); -------------------------------------------------------------------------------- /js/world/SosoTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/TiledLayer"], function(TiledLayer) { 2 | var SosoTiledLayer = function(args) { 3 | TiledLayer.apply(this, arguments); 4 | }; 5 | SosoTiledLayer.prototype = new TiledLayer(); 6 | SosoTiledLayer.prototype.constructor = SosoTiledLayer; 7 | SosoTiledLayer.prototype.getImageUrl = function(level, row, column) { 8 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 9 | var url = ""; 10 | var tileCount = Math.pow(2, level); 11 | var a = column; 12 | var b = tileCount - row - 1; 13 | var A = Math.floor(a / 16); 14 | var B = Math.floor(b / 16); 15 | var sum = level + row + column; 16 | var serverIdx = sum % 4; //0、1、2、3 17 | var sateUrl = "//p" + serverIdx + ".map.soso.com/sateTiles/" + level + "/" + A + "/" + B + "/" + a + "_" + b + ".jpg"; 18 | //var maptileUrl = "http://p"+serverIdx+".map.soso.com/maptilesv2/"+level+"/"+A+"/"+B+"/"+a+"_"+b+".png"; 19 | url = sateUrl; 20 | return url; 21 | }; 22 | return SosoTiledLayer; 23 | }); -------------------------------------------------------------------------------- /js/world/SubTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Utils", "world/Math", "world/Object3DComponents", "world/Tile", "world/Elevation"], 2 | function(Kernel, Utils, MathUtils, Object3DComponents, Tile, Elevation) { 3 | var SubTiledLayer = function(args) { 4 | Object3DComponents.apply(this, arguments); 5 | this.level = -1; 6 | //该级要请求的高程数据的层级,7[8,9,10];10[11,12,13];13[14,15,16];16[17,18,19] 7 | this.elevationLevel = -1; 8 | this.tiledLayer = null; 9 | if (args) { 10 | if (args.level !== undefined) { 11 | this.level = args.level; 12 | this.elevationLevel = Elevation.getAncestorElevationLevel(this.level); 13 | } 14 | } 15 | }; 16 | SubTiledLayer.prototype = new Object3DComponents(); 17 | SubTiledLayer.prototype.constructor = SubTiledLayer; 18 | //重写draw方法 19 | SubTiledLayer.prototype.draw = function(camera) { 20 | if (this.level >= Kernel.TERRAIN_LEVEL && Kernel.globe && Kernel.globe.pitch <= Kernel.TERRAIN_PITCH) { 21 | gl.clear(gl.DEPTH_BUFFER_BIT); 22 | gl.clearDepth(1); 23 | gl.enable(gl.DEPTH_TEST); 24 | } else { 25 | gl.disable(gl.DEPTH_TEST); 26 | } 27 | Object3DComponents.prototype.draw.apply(this, arguments); 28 | }; 29 | //重写Object3DComponents的add方法 30 | SubTiledLayer.prototype.add = function(tile) { 31 | if (!(tile instanceof Tile)) { 32 | throw "invalid tile: not Tile"; 33 | } 34 | if (tile.level == this.level) { 35 | Object3DComponents.prototype.add.apply(this, arguments); 36 | tile.subTiledLayer = this; 37 | } 38 | }; 39 | //调用其父的getImageUrl 40 | SubTiledLayer.prototype.getImageUrl = function(level, row, column) { 41 | if (!Utils.isNonNegativeInteger(level)) { 42 | throw "invalid level"; 43 | } 44 | if (!Utils.isNonNegativeInteger(row)) { 45 | throw "invalid row"; 46 | } 47 | if (!Utils.isNonNegativeInteger(column)) { 48 | throw "invalid column"; 49 | } 50 | var url = ""; 51 | if (this.tiledLayer) { 52 | url = this.tiledLayer.getImageUrl(level, row, column); 53 | } 54 | return url; 55 | }; 56 | //重写Object3DComponents的destroy方法 57 | SubTiledLayer.prototype.destroy = function() { 58 | Object3DComponents.prototype.destroy.apply(this, arguments); 59 | this.tiledLayer = null; 60 | }; 61 | //根据level、row、column查找tile,可以供调试用 62 | SubTiledLayer.prototype.findTile = function(level, row, column) { 63 | if (!Utils.isNonNegativeInteger(level)) { 64 | throw "invalid level"; 65 | } 66 | if (!Utils.isNonNegativeInteger(row)) { 67 | throw "invalid row"; 68 | } 69 | if (!Utils.isNonNegativeInteger(column)) { 70 | throw "invalid column"; 71 | } 72 | var length = this.children.length; 73 | for (var i = 0; i < length; i++) { 74 | var tile = this.children[i]; 75 | if (tile.level == level && tile.row == row && tile.column == column) { 76 | return tile; 77 | } 78 | } 79 | return null; 80 | }; 81 | //根据传入的tiles信息进行更新其children 82 | SubTiledLayer.prototype.updateTiles = function(visibleTileGrids, bAddNew) { //camera,options 83 | //var visibleTileGrids = camera.getVisibleTilesByLevel(this.level,options); 84 | //检查visibleTileGrids中是否存在指定的切片信息 85 | function checkTileExist(tileArray, lev, row, col) { 86 | var result = { 87 | isExist: false, 88 | index: -1 89 | }; 90 | for (var m = 0; m < tileArray.length; m++) { 91 | var tileInfo = tileArray[m]; 92 | if (tileInfo.level == lev && tileInfo.row == row && tileInfo.column == col) { 93 | result.isExist = true; 94 | result.index = m; 95 | return result; 96 | } 97 | } 98 | return result; 99 | } 100 | 101 | //记录应该删除的切片 102 | var tilesNeedDelete = []; 103 | var i, tile; 104 | for (i = 0; i < this.children.length; i++) { 105 | tile = this.children[i]; 106 | var checkResult = checkTileExist(visibleTileGrids, tile.level, tile.row, tile.column); 107 | var isExist = checkResult.isExist; 108 | if (isExist) { 109 | visibleTileGrids.splice(checkResult.index, 1); //已处理 110 | } else { 111 | //暂时不删除,先添加要删除的标记,循环删除容易出错 112 | tilesNeedDelete.push(tile); 113 | } 114 | } 115 | 116 | //集中进行删除 117 | while (tilesNeedDelete.length > 0) { 118 | var b = this.remove(tilesNeedDelete[0]); 119 | tilesNeedDelete.splice(0, 1); 120 | if (!b) { 121 | console.debug("LINE:2191,subTiledLayer.remove(tilesNeedDelete[0])失败"); 122 | } 123 | } 124 | 125 | if (bAddNew) { 126 | //添加新增的切片 127 | for (i = 0; i < visibleTileGrids.length; i++) { 128 | var tileGridInfo = visibleTileGrids[i]; 129 | var args = { 130 | level: tileGridInfo.level, 131 | row: tileGridInfo.row, 132 | column: tileGridInfo.column, 133 | url: "" 134 | }; 135 | args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); 136 | tile = new Tile(args); 137 | this.add(tile); 138 | } 139 | } 140 | }; 141 | //如果bForce为true,则表示强制显示为三维,不考虑level 142 | SubTiledLayer.prototype.checkTerrain = function(bForce) { 143 | var globe = Kernel.globe; 144 | var show3d = bForce === true ? true : this.level >= Kernel.TERRAIN_LEVEL; 145 | if (show3d && globe && globe.camera && globe.camera.pitch < Kernel.TERRAIN_PITCH) { 146 | var tiles = this.children; 147 | for (var i = 0; i < tiles.length; i++) { 148 | var tile = tiles[i]; 149 | tile.checkTerrain(bForce); 150 | } 151 | } 152 | }; 153 | 154 | 155 | //根据当前子图层下的tiles获取其对应的祖先高程切片的TileGrid //getAncestorElevationTileGrids 156 | //7 8 9 10; 10 11 12 13; 13 14 15 16; 16 17 18 19; 157 | SubTiledLayer.prototype.requestElevations = function() { 158 | var result = []; 159 | if (this.level > Kernel.ELEVATION_LEVEL) { 160 | var tiles = this.children; 161 | var i, name; 162 | for (i = 0; i < tiles.length; i++) { 163 | var tile = tiles[i]; 164 | var tileGrid = MathUtils.getTileGridAncestor(this.elevationLevel, tile.level, tile.row, tile.column); 165 | name = tileGrid.level + "_" + tileGrid.row + "_" + tileGrid.column; 166 | if (result.indexOf(name) < 0) { 167 | result.push(name); 168 | } 169 | } 170 | for (i = 0; i < result.length; i++) { 171 | name = result[i]; 172 | var a = name.split('_'); 173 | var eleLevel = parseInt(a[0]); 174 | var eleRow = parseInt(a[1]); 175 | var eleColumn = parseInt(a[2]); 176 | //只要elevations中有属性name,那么就表示该高程已经请求过或正在请求,这样就不要重新请求了 177 | //只有在完全没请求过的情况下去请求高程数据 178 | if (!Elevation.elevations.hasOwnProperty(name)) { 179 | Elevation.requestElevationsByTileGrid(eleLevel, eleRow, eleColumn); 180 | } 181 | } 182 | } 183 | }; 184 | SubTiledLayer.prototype.checkIfLoaded = function() { 185 | for (var i = 0; i < this.children.length; i++) { 186 | var tile = this.children[i]; 187 | if (tile) { 188 | var isTileLoaded = tile.material.loaded; 189 | if (!isTileLoaded) { 190 | return false; 191 | } 192 | } 193 | } 194 | return true; 195 | }; 196 | return SubTiledLayer; 197 | }); -------------------------------------------------------------------------------- /js/world/TextureMaterial.js: -------------------------------------------------------------------------------- 1 | define(["world/Utils"], function(Utils) { 2 | var TextureMaterial = function(args) { 3 | if (args) { 4 | this.texture = gl.createTexture(); 5 | this.image = null; 6 | this.loaded = false; 7 | this.delete = false; 8 | if (args.image instanceof Image && args.image.width > 0 && args.image.height > 0) { 9 | this.setImage(args.image); 10 | } else if (typeof args.url == "string") { 11 | this.setImageUrl(args.url); 12 | } 13 | } 14 | }; 15 | 16 | TextureMaterial.prototype.setImage = function(image) { 17 | if (image instanceof Image && image.width > 0 && image.height > 0) { 18 | this.image = image; 19 | this.onLoad(); 20 | } 21 | }; 22 | TextureMaterial.prototype.setImageUrl = function(url) { 23 | if (!Utils.isString(url)) { 24 | throw "invalid url: not string"; 25 | } 26 | this.image = new Image(); 27 | this.image.crossOrigin = 'anonymous'; //很重要,因为图片是跨域获得的,所以一定要加上此句代码 28 | this.image.onload = this.onLoad.bind(this); 29 | this.image.src = url; 30 | }; 31 | //图片加载完成时触发 32 | TextureMaterial.prototype.onLoad = function() { 33 | //要考虑纹理已经被移除掉了图片才进入onLoad这种情况 34 | if (this.delete) { 35 | return; 36 | } 37 | 38 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 39 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 40 | 41 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.image); 42 | //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 43 | //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 44 | //使用MipMap 45 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); 46 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); //LINEAR_MIPMAP_NEAREST LINEAR_MIPMAP_LINEAR 47 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 48 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 49 | gl.generateMipmap(gl.TEXTURE_2D); 50 | gl.bindTexture(gl.TEXTURE_2D, null); 51 | this.loaded = true; 52 | }; 53 | //释放显卡中的texture资源 54 | TextureMaterial.prototype.releaseTexture = function() { 55 | if (gl.isTexture(this.texture)) { 56 | gl.deleteTexture(this.texture); 57 | this.delete = true; 58 | } 59 | }; 60 | return TextureMaterial; 61 | }); -------------------------------------------------------------------------------- /js/world/TiandituTiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/TiledLayer"], function(TiledLayer) { 2 | var TiandituTiledLayer = function(args) { 3 | TiledLayer.apply(this, arguments); 4 | }; 5 | TiandituTiledLayer.prototype = new TiledLayer(); 6 | TiandituTiledLayer.prototype.constructor = TiandituTiledLayer; 7 | TiandituTiledLayer.prototype.getImageUrl = function(level, row, column) { 8 | TiledLayer.prototype.getImageUrl.apply(this, arguments); 9 | var url = ""; 10 | var sum = level + row + column; 11 | var serverIdx = sum % 8; 12 | url = "//t" + serverIdx + ".tianditu.com/DataServer?T=vec_w&x=" + column + "&y=" + row + "&l=" + level; 13 | return url; 14 | }; 15 | return TiandituTiledLayer; 16 | }); -------------------------------------------------------------------------------- /js/world/Tile.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Object3D", "world/Enum", "world/Elevation", "world/Math", "world/TileMaterial"], 2 | function(Kernel, Object3D, Enum, Elevation, MathUtils, TileMaterial) { 3 | //args中包含level、row、column、url即可 4 | var Tile = function(args) { 5 | if (args) { 6 | this.subTiledLayer = null; 7 | //type如果是GLOBE_TILE,表示其buffer已经设置为一般形式 8 | //type如果是TERRAIN_TILE,表示其buffer已经设置为高程形式 9 | //type如果是UNKNOWN,表示buffer没设置 10 | this.type = Enum.UNKNOWN; 11 | this.level = 0; 12 | this.row = 0; 13 | this.column = 0; 14 | this.url = args.url; 15 | this.elevationLevel = 0; //高程level 16 | this.minLon = null; 17 | this.minLat = null; 18 | this.maxLon = null; 19 | this.maxLat = null; 20 | this.minX = null; 21 | this.minY = null; 22 | this.maxX = null; 23 | this.maxY = null; 24 | this.elevationInfo = null; 25 | Object3D.apply(this, arguments); 26 | } 27 | }; 28 | Tile.prototype = new Object3D(); 29 | Tile.prototype.constructor = Tile; 30 | Tile.prototype.createVerticeData = function(args) { 31 | if (!args) { 32 | return; 33 | } 34 | this.setTileInfo(args); 35 | this.checkTerrain(); 36 | }; 37 | 38 | // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 39 | Tile.prototype.setTileInfo = function(args) { 40 | this.level = args.level; 41 | this.row = args.row; 42 | this.column = args.column; 43 | this.elevationLevel = Elevation.getAncestorElevationLevel(this.level); 44 | //经纬度范围 45 | var Egeo = MathUtils.getTileGeographicEnvelopByGrid(this.level, this.row, this.column); 46 | this.minLon = Egeo.minLon; 47 | this.minLat = Egeo.minLat; 48 | this.maxLon = Egeo.maxLon; 49 | this.maxLat = Egeo.maxLat; 50 | var minCoord = MathUtils.degreeGeographicToWebMercator(this.minLon, this.minLat); 51 | var maxCoord = MathUtils.degreeGeographicToWebMercator(this.maxLon, this.maxLat); 52 | //投影坐标范围 53 | this.minX = minCoord[0]; 54 | this.minY = minCoord[1]; 55 | this.maxX = maxCoord[0]; 56 | this.maxY = maxCoord[1]; 57 | var matArgs = { 58 | level: this.level, 59 | url: this.url 60 | }; 61 | this.material = new TileMaterial(matArgs); 62 | }; 63 | 64 | /** 65 | * 判断是否满足现实Terrain的条件,若满足则转换为三维地形 66 | * 条件: 67 | * 1.当前显示的是GlobeTile 68 | * 2.该切片的level大于TERRAIN_LEVEL 69 | * 3.pich不为90 70 | * 4.当前切片的高程数据存在 71 | * 5.如果bForce为true,则表示强制显示为三维,不考虑level 72 | */ 73 | Tile.prototype.checkTerrain = function(bForce) { 74 | var globe = Kernel.globe; 75 | var a = bForce === true ? true : this.level >= Kernel.TERRAIN_LEVEL; 76 | var shouldShowTerrain = this.type != Enum.TERRAIN_TILE && a && globe && globe.camera && globe.camera.pitch != 90; 77 | if (shouldShowTerrain) { 78 | //应该以TerrainTile显示 79 | if (!this.elevationInfo) { 80 | this.elevationInfo = Elevation.getExactElevation(this.level, this.row, this.column); 81 | 82 | // if(this.level - this.elevationLevel == 1){ 83 | // //当该level与其elevationLevel只相差一级时,可以使用推倒的高程数据 84 | // this.elevationInfo = Elevation.getElevation(this.level,this.row,this.column); 85 | // if(this.elevationInfo){ 86 | // console.log("Tile("+this.level+","+this.row+","+this.column+");sourceLevel:"+this.elevationInfo.sourceLevel+";elevationLevel:"+this.elevationLevel); 87 | // } 88 | // } 89 | // else{ 90 | // //否则使用准确的高程数据 91 | // this.elevationInfo = Elevation.getExactElevation(this.level,this.row,this.column); 92 | // } 93 | } 94 | var canShowTerrain = this.elevationInfo ? true : false; 95 | if (canShowTerrain) { 96 | //能够显示为TerrainTile 97 | this.handleTerrainTile(); 98 | } else { 99 | //不能够显示为TerrainTile 100 | this.visible = false; 101 | //this.handleGlobeTile(); 102 | } 103 | } else { 104 | if (this.type == Enum.UNKNOWN) { 105 | //初始type为UNKNOWN,还未初始化buffer,应该显示为GlobeTile 106 | this.handleGlobeTile(); 107 | } 108 | } 109 | }; 110 | 111 | //处理球面的切片 112 | Tile.prototype.handleGlobeTile = function() { 113 | this.type = Enum.GLOBE_TILE; 114 | if (this.level < Kernel.BASE_LEVEL) { 115 | var changeLevel = Kernel.BASE_LEVEL - this.level; 116 | this.segment = Math.pow(2, changeLevel); 117 | } else { 118 | this.segment = 1; 119 | } 120 | this.handleTile(); 121 | }; 122 | //处理地形的切片 123 | Tile.prototype.handleTerrainTile = function() { 124 | this.type = Enum.TERRAIN_TILE; 125 | this.segment = 10; 126 | this.handleTile(); 127 | }; 128 | 129 | //如果是GlobeTile,那么elevations为null 130 | //如果是TerrainTile,那么elevations是一个一维数组,大小是(segment+1)*(segment+1) 131 | Tile.prototype.handleTile = function() { 132 | this.visible = true; 133 | var vertices = []; 134 | var indices = []; 135 | var textureCoords = []; 136 | 137 | var deltaX = (this.maxX - this.minX) / this.segment; 138 | var deltaY = (this.maxY - this.minY) / this.segment; 139 | var deltaTextureCoord = 1.0 / this.segment; 140 | var changeElevation = this.type == Enum.TERRAIN_TILE && this.elevationInfo; 141 | //level不同设置的半径也不同 142 | var levelDeltaR = 0; //this.level * 100; 143 | //对WebMercator投影进行等间距划分格网 144 | var mercatorXs = []; //存储从最小的x到最大x的分割值 145 | var mercatorYs = []; //存储从最大的y到最小的y的分割值 146 | var textureSs = []; //存储从0到1的s的分割值 147 | var textureTs = []; //存储从1到0的t的分割值 148 | var i, j; 149 | 150 | for (i = 0; i <= this.segment; i++) { 151 | mercatorXs.push(this.minX + i * deltaX); 152 | mercatorYs.push(this.maxY - i * deltaY); 153 | var b = i * deltaTextureCoord; 154 | textureSs.push(b); 155 | textureTs.push(1 - b); 156 | } 157 | //从左上到右下遍历填充vertices和textureCoords:从最上面一行开始自左向右遍历一行,然后再以相同的方式遍历下面一行 158 | for (i = 0; i <= this.segment; i++) { 159 | for (j = 0; j <= this.segment; j++) { 160 | var merX = mercatorXs[j]; 161 | var merY = mercatorYs[i]; 162 | var ele = changeElevation ? this.elevationInfo.elevations[(this.segment + 1) * i + j] : 0; 163 | var lonlat = MathUtils.webMercatorToDegreeGeographic(merX, merY); 164 | var p = MathUtils.geographicToCartesianCoord(lonlat[0], lonlat[1], Kernel.EARTH_RADIUS + ele + levelDeltaR).getArray(); 165 | vertices = vertices.concat(p); //顶点坐标 166 | textureCoords = textureCoords.concat(textureSs[j], textureTs[i]); //纹理坐标 167 | } 168 | } 169 | 170 | //从左上到右下填充indices 171 | //添加的点的顺序:左上->左下->右下->右上 172 | //0 1 2; 2 3 0; 173 | /*对于一个面从外面向里面看的绘制顺序 174 | * 0 3 175 | * 176 | * 1 2*/ 177 | for (i = 0; i < this.segment; i++) { 178 | for (j = 0; j < this.segment; j++) { 179 | var idx0 = (this.segment + 1) * i + j; 180 | var idx1 = (this.segment + 1) * (i + 1) + j; 181 | var idx2 = idx1 + 1; 182 | var idx3 = idx0 + 1; 183 | indices = indices.concat(idx0, idx1, idx2); // 0 1 2 184 | indices = indices.concat(idx2, idx3, idx0); // 2 3 0 185 | } 186 | } 187 | 188 | // if(changeElevation){ 189 | // //添加坐标原点的数据 190 | // var originVertice = [0,0,0]; 191 | // var originTexture = [0,0]; 192 | // vertices = vertices.concat(originVertice); 193 | // textureCoords = textureCoords.concat(originTexture); 194 | // 195 | // var idxOrigin = (this.segment+1)*(this.segment+1); 196 | // var idxLeftTop = 0; 197 | // var idxRightTop = this.segment; 198 | // var idxRightBottom = (this.segment+1)*(this.segment+1)-1; 199 | // var idxLeftBottom = idxRightBottom - this.segment; 200 | // indices = indices.concat(idxLeftTop,idxOrigin,idxLeftBottom); 201 | // indices = indices.concat(idxRightTop,idxOrigin,idxLeftTop); 202 | // indices = indices.concat(idxRightBottom,idxOrigin,idxRightTop); 203 | // indices = indices.concat(idxLeftBottom,idxOrigin,idxRightBottom); 204 | // } 205 | 206 | var infos = { 207 | vertices: vertices, 208 | indices: indices, 209 | textureCoords: textureCoords 210 | }; 211 | this.setBuffers(infos); 212 | }; 213 | //重写Object3D的destroy方法 214 | Tile.prototype.destroy = function() { 215 | Object3D.prototype.destroy.apply(this, arguments); 216 | this.subTiledLayer = null; 217 | }; 218 | 219 | return Tile; 220 | }); -------------------------------------------------------------------------------- /js/world/TileGrid.js: -------------------------------------------------------------------------------- 1 | define(["world/Utils", "world/Math"], function(Utils) { 2 | 3 | var TileGrid = function(level, row, column) { 4 | if (!Utils.isNonNegativeInteger(level)) { 5 | throw "invalid level"; 6 | } 7 | if (!Utils.isNonNegativeInteger(row)) { 8 | throw "invalid row"; 9 | } 10 | if (!Utils.isNonNegativeInteger(column)) { 11 | throw "invalid column"; 12 | } 13 | this.level = level; 14 | this.row = row; 15 | this.column = column; 16 | }; 17 | TileGrid.prototype._requireMath = function(){ 18 | return require("world/Math"); 19 | }; 20 | TileGrid.prototype.equals = function(other) { 21 | return other instanceof TileGrid && this.level == other.level && this.row == other.row && this.column == other.column; 22 | }; 23 | TileGrid.prototype.getLeft = function() { 24 | var MathUtils = this._requireMath(); 25 | return MathUtils.getTileGridByBrother(this.level, this.row, this.column, MathUtils.LEFT); 26 | }; 27 | TileGrid.prototype.getRight = function() { 28 | var MathUtils = this._requireMath(); 29 | return MathUtils.getTileGridByBrother(this.level, this.row, this.column, MathUtils.RIGHT); 30 | }; 31 | TileGrid.prototype.getTop = function() { 32 | var MathUtils = this._requireMath(); 33 | return MathUtils.getTileGridByBrother(this.level, this.row, this.column, MathUtils.TOP); 34 | }; 35 | TileGrid.prototype.getBottom = function() { 36 | var MathUtils = this._requireMath(); 37 | return MathUtils.getTileGridByBrother(this.level, this.row, this.column, MathUtils.BOTTOM); 38 | }; 39 | TileGrid.prototype.getParent = function() { 40 | var MathUtils = this._requireMath(); 41 | return MathUtils.getTileGridAncestor(this.level - 1, this.level, this.row, this.column); 42 | }; 43 | TileGrid.prototype.getAncestor = function(ancestorLevel) { 44 | var MathUtils = this._requireMath(); 45 | return MathUtils.getTileGridAncestor(ancestorLevel, this.level, this.row, this.column); 46 | }; 47 | 48 | return TileGrid; 49 | }); -------------------------------------------------------------------------------- /js/world/TileMaterial.js: -------------------------------------------------------------------------------- 1 | define(["world/TextureMaterial", "world/Image"], function(TextureMaterial, Image1) { 2 | //TileMaterial继承自TextureMaterial 3 | var TileMaterial = function(args) { 4 | if (args) { 5 | if (!args.image && typeof args.url == "string") { 6 | var tileImage = Image1.get(args.url); 7 | if (tileImage) { 8 | args.image = tileImage; 9 | delete args.url; 10 | } 11 | } 12 | this.level = typeof args.level == "number" && args.level >= 0 ? args.level : 20; 13 | TextureMaterial.apply(this, arguments); 14 | } 15 | }; 16 | TileMaterial.prototype = new TextureMaterial(); 17 | TileMaterial.prototype.constructor = TileMaterial; 18 | TileMaterial.prototype.onLoad = function(event) { 19 | if (this.level <= Image1.MAX_LEVEL) { 20 | Image1.add(this.image.src, this.image); 21 | } 22 | TextureMaterial.prototype.onLoad.apply(this, arguments); 23 | }; 24 | return TileMaterial; 25 | }); -------------------------------------------------------------------------------- /js/world/TiledLayer.js: -------------------------------------------------------------------------------- 1 | define(["world/Object3DComponents", "world/SubTiledLayer", "world/Utils"], function(Object3DComponents, SubTiledLayer, Utils) { 2 | var TiledLayer = function(args) { 3 | Object3DComponents.apply(this, arguments); 4 | }; 5 | TiledLayer.prototype = new Object3DComponents(); 6 | TiledLayer.prototype.constructor = TiledLayer; 7 | //重写 8 | TiledLayer.prototype.add = function(subTiledLayer) { 9 | if (!(subTiledLayer instanceof SubTiledLayer)) { 10 | throw "invalid subTiledLayer: not World.SubTiledLayer"; 11 | } 12 | Object3DComponents.prototype.add.apply(this, arguments); 13 | subTiledLayer.tiledLayer = this; 14 | }; 15 | //根据切片的层级以及行列号获取图片的url,抽象方法,供子类实现 16 | TiledLayer.prototype.getImageUrl = function(level, row, column) { 17 | if (!Utils.isNonNegativeInteger(level)) { 18 | throw "invalid level"; 19 | } 20 | if (!Utils.isNonNegativeInteger(row)) { 21 | throw "invalid row"; 22 | } 23 | if (!Utils.isNonNegativeInteger(column)) { 24 | throw "invalid column"; 25 | } 26 | return ""; 27 | }; 28 | //根据传入的level更新SubTiledLayer的数量 29 | TiledLayer.prototype.updateSubLayerCount = function(level) { 30 | if (!Utils.isNonNegativeInteger(level)) { 31 | throw "invalid level"; 32 | } 33 | var subLayerCount = this.children.length; 34 | var deltaLevel = level + 1 - subLayerCount; 35 | var i, subLayer; 36 | if (deltaLevel > 0) { 37 | //需要增加子图层 38 | for (i = 0; i < deltaLevel; i++) { 39 | var args = { 40 | level: i + subLayerCount 41 | }; 42 | subLayer = new SubTiledLayer(args); 43 | this.add(subLayer); 44 | } 45 | } else if (deltaLevel < 0) { 46 | //需要删除多余的子图层 47 | deltaLevel *= -1; 48 | for (i = 0; i < deltaLevel; i++) { 49 | var removeLevel = this.children.length - 1; 50 | //第0级和第1级不删除 51 | if (removeLevel >= 2) { 52 | subLayer = this.children[removeLevel]; 53 | this.remove(subLayer); 54 | } else { 55 | break; 56 | } 57 | } 58 | } 59 | }; 60 | return TiledLayer; 61 | }); -------------------------------------------------------------------------------- /js/world/Utils.js: -------------------------------------------------------------------------------- 1 | define({ 2 | GREATER:"GREATER", 3 | GEQUAL:"GEQUAL", 4 | LESS:"LESS", 5 | LEQUAL:"LEQUAL", 6 | 7 | isBool:function(v){ 8 | return typeof v == "boolean"; 9 | }, 10 | 11 | isNumber:function(v){ 12 | return typeof v == "number"; 13 | }, 14 | 15 | isInteger:function(v){ 16 | var isInt = false; 17 | var isNum = this.isNumber(v); 18 | if(isNum){ 19 | var numFloat = parseFloat(v); 20 | var numInt = parseInt(v); 21 | if(numFloat == numInt){ 22 | isInt = true; 23 | } 24 | else{ 25 | isInt = false; 26 | } 27 | } 28 | else{ 29 | isInt = false; 30 | } 31 | return isInt; 32 | }, 33 | 34 | judgeNumberBoundary:function(num,operator,boundry){ 35 | if(!this.isNumber(num)){ 36 | throw "num is not number"; 37 | } 38 | if(operator != this.GREATER && operator != this.GEQUAL && operator != this.LESS && operator != this.LEQUAL){ 39 | throw "operator is invalid"; 40 | } 41 | if(!this.isNumber(boundry)){ 42 | throw "boundry is not number"; 43 | } 44 | var b; 45 | if(operator == this.GREATER){ 46 | b = num > boundry; 47 | } 48 | else if(operator == this.GEQUAL){ 49 | b = num >= boundry; 50 | } 51 | else if(operator == this.LESS){ 52 | b = num < boundry; 53 | } 54 | else if(operator == this.LEQUAL){ 55 | b = num <= boundry; 56 | } 57 | return b; 58 | }, 59 | 60 | isPositive:function(v){ 61 | return this.judgeNumberBoundary(v,this.GREATER,0); 62 | }, 63 | 64 | isNegative:function(v){ 65 | return this.judgeNumberBoundary(v,this.LESS,0); 66 | }, 67 | 68 | isNonNegative:function(v){ 69 | return this.judgeNumberBoundary(v,this.GEQUAL,0); 70 | }, 71 | 72 | isNonPositive:function(v){ 73 | return this.judgeNumberBoundary(v,this.LEQUAL,0); 74 | }, 75 | 76 | isPositiveInteger:function(v){ 77 | return this.isPositive(v) && this.isInteger(v); 78 | }, 79 | 80 | isNonNegativeInteger:function(v){ 81 | return this.isNonNegative(v) && this.isInteger; 82 | }, 83 | 84 | isString:function(v){ 85 | return typeof v == "string"; 86 | }, 87 | 88 | isArray:function(v){ 89 | return Object.prototype.toString.call(v) === '[object Array]'; 90 | }, 91 | 92 | isFunction:function(v){ 93 | return typeof v == "function"; 94 | }, 95 | 96 | isNull:function(v){ 97 | return v === null; 98 | }, 99 | 100 | isUndefined:function(v){ 101 | return typeof v == "undefined"; 102 | }, 103 | 104 | isNullOrUndefined:function(v){ 105 | return this.isNull(v)||this.isUndefined(v); 106 | }, 107 | 108 | isJsonObject:function(v){ 109 | return typeof v == "object" && !this.isNull(v) && !this.isArray(v); 110 | }, 111 | 112 | isDom:function(v){ 113 | return v instanceof HTMLElement; 114 | }, 115 | 116 | forEach:function(arr,func){ 117 | if(!(this.isArray(arr))){ 118 | throw "invalid arr"; 119 | } 120 | if(!(this.isFunction(func))){ 121 | throw "invalid func"; 122 | } 123 | if(this.isFunction(Array.prototype.forEach)){ 124 | arr.forEach(func); 125 | } 126 | else{ 127 | for(var i=0;i 0){ 223 | var e = cloneArray[0]; 224 | var exist = this.some(simplifyArray,function(item){ 225 | return e.equals(item); 226 | }); 227 | if(!exist){ 228 | simplifyArray.push(e); 229 | } 230 | cloneArray.splice(0,1); 231 | } 232 | return simplifyArray; 233 | } 234 | }); -------------------------------------------------------------------------------- /js/world/Vector.js: -------------------------------------------------------------------------------- 1 | define(["require", "world/Utils", "world/Vertice"], function(require, Utils) { 2 | 3 | var Vector = function(x, y, z) { 4 | x = x !== undefined ? x : 0; 5 | y = y !== undefined ? y : 0; 6 | z = z !== undefined ? z : 0; 7 | if (!Utils.isNumber(x)) { 8 | throw "invalid x"; 9 | } 10 | if (!Utils.isNumber(y)) { 11 | throw "invalid y"; 12 | } 13 | if (!Utils.isNumber(z)) { 14 | throw "invalid z"; 15 | } 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | }; 20 | 21 | Vector.prototype = { 22 | constructor: Vector, 23 | 24 | _requireVertice: function(){ 25 | return require("world/Vertice"); 26 | }, 27 | 28 | getVertice: function() { 29 | var Vertice = this._requireVertice(); 30 | return new Vertice(this.x, this.y, this.z); 31 | }, 32 | 33 | getArray: function() { 34 | return [this.x, this.y, this.z]; 35 | }, 36 | 37 | getCopy: function() { 38 | return new Vector(this.x, this.y, this.z); 39 | }, 40 | 41 | getOpposite: function() { 42 | return new Vector(-this.x, -this.y, -this.z); 43 | }, 44 | 45 | getLength: function() { 46 | return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); 47 | }, 48 | 49 | normalize: function() { 50 | var length = this.getLength(); 51 | if(Math.abs(length) >= 0.000001) { 52 | this.x /= length; 53 | this.y /= length; 54 | this.z /= length; 55 | } else { 56 | this.x = 0; 57 | this.y = 0; 58 | this.z = 0; 59 | } 60 | 61 | return this; 62 | }, 63 | 64 | setLength: function(length) { 65 | if (!Utils.isNumber(length)) { 66 | throw "invalid length"; 67 | } 68 | this.normalize(); 69 | this.x *= length; 70 | this.y *= length; 71 | this.z *= length; 72 | return this; 73 | }, 74 | 75 | /** 76 | * 得到该向量的一个随机垂直向量 77 | * @return {*} 78 | */ 79 | getRandomVerticalVector: function() { 80 | var result; 81 | var length = this.getLength(); 82 | if (length === 0) { 83 | result = new Vector(0, 0, 0); 84 | } else { 85 | var x2, y2, z2; 86 | if (this.x !== 0) { 87 | y2 = 1; 88 | z2 = 0; 89 | x2 = -this.y / this.x; 90 | } else if (this.y !== 0) { 91 | z2 = 1; 92 | x2 = 0; 93 | y2 = -this.z / this.y; 94 | } else if (this.z !== 0) { 95 | x2 = 1; 96 | y2 = 0; 97 | z2 = -this.x / this.z; 98 | } 99 | result = new Vector(x2, y2, z2); 100 | result.normalize(); 101 | } 102 | return result; 103 | }, 104 | 105 | /** 106 | * 计算与另一个向量的叉乘 107 | * @param other 108 | * @return {World.Vector} 109 | */ 110 | cross: function(other) { 111 | if (!(other instanceof Vector)) { 112 | throw "invalid other"; 113 | } 114 | var x = this.y * other.z - this.z * other.y; 115 | var y = this.z * other.x - this.x * other.z; 116 | var z = this.x * other.y - this.y * other.x; 117 | return new Vector(x, y, z); 118 | }, 119 | 120 | /** 121 | * 计算与另一个向量的点乘 122 | * @param other 另一个向量 123 | * @return {*} 数字 124 | */ 125 | dot: function(other) { 126 | if (!(other instanceof Vector)) { 127 | throw "invalid other"; 128 | } 129 | return this.x * other.x + this.y * other.y + this.z * other.z; 130 | } 131 | 132 | /** 133 | * 得到某点绕该向量旋转radian弧度后的新点 134 | * @param vertice 初始点 135 | * @param radian 旋转的角度 136 | * @return {World.Vertice} 被旋转之后的点 137 | */ 138 | /*rotateVertice: function(vertice, radian) { 139 | var Matrix = this._requireMatrix(); 140 | var Vertice = this._requireVertice(); 141 | if (!(vertice instanceof Vertice)) { 142 | throw "invalid vertice"; 143 | } 144 | if (!Utils.isNumber(radian)) { 145 | throw "invalid radian"; 146 | } 147 | var mat = new Matrix(); 148 | mat.worldRotateByVector(radian, this); 149 | var point = [vertice.x, vertice.y, vertice.z, 1]; 150 | var newPoint = mat.multiplyColumn(point); 151 | var newVertice = new Vertice(newPoint[0], newPoint[1], newPoint[2]); 152 | return newVertice; 153 | },*/ 154 | 155 | /** 156 | * 得到other向量绕该向量旋转radian弧度后的新向量 157 | * @param other 初始向量 158 | * @param radian 旋转的角度 159 | * @return {World.Vector} 被旋转之后的向量 160 | */ 161 | /*rotateVector: function(otherVector, radian) { 162 | if (!(otherVector instanceof Vector)) { 163 | throw "invalid otherVector"; 164 | } 165 | if (!Utils.isNumber(radian)) { 166 | throw "invalid radian"; 167 | } 168 | var vertice = otherVector.getVertice(); 169 | var newVertice = this.rotateVertice(vertice, radian); 170 | var newVector = newVertice.getVector(); 171 | return newVector; 172 | }*/ 173 | }; 174 | 175 | return Vector; 176 | }); -------------------------------------------------------------------------------- /js/world/Vertice.js: -------------------------------------------------------------------------------- 1 | define(["require", "world/Utils", "world/Vector"], function(require, Utils) { 2 | 3 | var Vertice = function(x, y, z) { 4 | x = x !== undefined ? x : 0; 5 | y = y !== undefined ? y : 0; 6 | z = z !== undefined ? z : 0; 7 | if (!Utils.isNumber(x)) { 8 | throw "invalid x"; 9 | } 10 | if (!Utils.isNumber(y)) { 11 | throw "invalid y"; 12 | } 13 | if (!Utils.isNumber(z)) { 14 | throw "invalid z"; 15 | } 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | }; 20 | 21 | Vertice.prototype = { 22 | constructor: Vertice, 23 | 24 | _requireVector: function(){ 25 | return require("world/Vector"); 26 | }, 27 | 28 | minus: function(otherVertice) { 29 | var Vector = this._requireVector(); 30 | if (!(otherVertice instanceof Vertice)) { 31 | throw "invalid otherVertice"; 32 | } 33 | var x = this.x - otherVertice.x; 34 | var y = this.y - otherVertice.y; 35 | var z = this.z - otherVertice.z; 36 | return new Vector(x, y, z); 37 | }, 38 | 39 | plus: function(otherVector) { 40 | var Vector = this._requireVector(); 41 | if (!(otherVector instanceof Vector)) { 42 | throw "invalid otherVector"; 43 | } 44 | var x = this.x + otherVector.x; 45 | var y = this.y + otherVector.y; 46 | var z = this.z + otherVector.z; 47 | return new Vertice(x, y, z); 48 | }, 49 | 50 | getVector: function() { 51 | var Vector = this._requireVector(); 52 | return new Vector(this.x, this.y, this.z); 53 | }, 54 | 55 | getArray: function() { 56 | return [this.x, this.y, this.z]; 57 | }, 58 | 59 | getCopy: function() { 60 | return new Vertice(this.x, this.y, this.z); 61 | }, 62 | 63 | getOpposite: function() { 64 | return new Vertice(-this.x, -this.y, -this.z); 65 | } 66 | }; 67 | 68 | return Vertice; 69 | }); -------------------------------------------------------------------------------- /js/world/WebGLRenderer.js: -------------------------------------------------------------------------------- 1 | define(["world/Kernel", "world/Utils", "world/Event", "world/Scene", "world/PerspectiveCamera"], 2 | function(Kernel, Utils, Event, Scene, PerspectiveCamera) { 3 | 4 | var WebGLRenderer = function(canvas, vertexShaderText, fragmentShaderText) { 5 | if (!(canvas instanceof HTMLCanvasElement)) { 6 | throw "invalid canvas: not HTMLCanvasElement"; 7 | } 8 | if (!(Utils.isString(vertexShaderText) && vertexShaderText !== "")) { 9 | throw "invalid vertexShaderText"; 10 | } 11 | if (!(Utils.isString(fragmentShaderText) && fragmentShaderText !== "")) { 12 | throw "invalid fragmentShaderText"; 13 | } 14 | window.renderer = this; //之所以在此处设置window.renderer是因为要在tick函数中使用 15 | this.scene = null; 16 | this.camera = null; 17 | this.bAutoRefresh = false; 18 | Event.bindEvents(canvas); 19 | 20 | function initWebGL(canvas) { 21 | try { 22 | var contextList = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]; 23 | for (var i = 0; i < contextList.length; i++) { 24 | var g = canvas.getContext(contextList[i], { 25 | antialias: true 26 | }); 27 | if (g) { 28 | window.gl = Kernel.gl = g; 29 | Kernel.canvas = canvas; 30 | break; 31 | } 32 | } 33 | } catch (e) {} 34 | } 35 | 36 | function getShader(gl, shaderType, shaderText) { 37 | if (!shaderText) { 38 | return null; 39 | } 40 | 41 | var shader = null; 42 | if (shaderType == "VERTEX_SHADER") { 43 | shader = gl.createShader(gl.VERTEX_SHADER); 44 | } else if (shaderType == "FRAGMENT_SHADER") { 45 | shader = gl.createShader(gl.FRAGMENT_SHADER); 46 | } else { 47 | return null; 48 | } 49 | 50 | gl.shaderSource(shader, shaderText); 51 | gl.compileShader(shader); 52 | 53 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 54 | alert(gl.getShaderInfoLog(shader)); 55 | console.error(gl.getShaderInfoLog(shader)); 56 | gl.deleteShader(shader); 57 | return null; 58 | } 59 | 60 | return shader; 61 | } 62 | 63 | function initShaders(vertexShaderText, fragmentShaderText) { 64 | var vertexShader = getShader(Kernel.gl, "VERTEX_SHADER", vertexShaderText); 65 | var fragmentShader = getShader(Kernel.gl, "FRAGMENT_SHADER", fragmentShaderText); 66 | 67 | var shaderProgram = gl.createProgram(); 68 | gl.attachShader(shaderProgram, vertexShader); 69 | gl.attachShader(shaderProgram, fragmentShader); 70 | gl.linkProgram(shaderProgram); 71 | 72 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 73 | console.error("Could not link program!"); 74 | gl.deleteProgram(shaderProgram); 75 | gl.deleteShader(vertexShader); 76 | gl.deleteShader(fragmentShader); 77 | return; 78 | } 79 | 80 | gl.useProgram(shaderProgram); 81 | gl.shaderProgram = shaderProgram; 82 | gl.shaderProgram.aVertexPosition = gl.getAttribLocation(gl.shaderProgram, "aVertexPosition"); 83 | gl.shaderProgram.aTextureCoord = gl.getAttribLocation(gl.shaderProgram, "aTextureCoord"); 84 | gl.shaderProgram.uMVMatrix = gl.getUniformLocation(gl.shaderProgram, "uMVMatrix"); 85 | gl.shaderProgram.uPMatrix = gl.getUniformLocation(gl.shaderProgram, "uPMatrix"); 86 | gl.shaderProgram.uSampler = gl.getUniformLocation(gl.shaderProgram, "uSampler"); //纹理采样器 87 | gl.shaderProgram.uOffScreen = gl.getUniformLocation(gl.shaderProgram, "uOffScreen"); //是否离屏渲染 88 | 89 | //设置默认非离屏渲染 90 | gl.uniform1i(gl.shaderProgram.uOffScreen, 1); 91 | 92 | //设置默认值 93 | var squareArray = [1, 0, 0, 0, 94 | 0, 1, 0, 0, 95 | 0, 0, 1, 0, 96 | 0, 0, 0, 1 97 | ]; 98 | var squareMatrix = new Float32Array(squareArray); //ArrayBuffer 99 | gl.uniformMatrix4fv(gl.shaderProgram.uMVMatrix, false, squareMatrix); 100 | } 101 | 102 | initWebGL(canvas); 103 | 104 | if (!window.gl) { 105 | alert("浏览器不支持WebGL或将WebGL禁用!"); 106 | console.debug("浏览器不支持WebGL或将WebGL禁用!"); 107 | return; 108 | } 109 | 110 | initShaders(vertexShaderText, fragmentShaderText); 111 | 112 | gl.clearColor(255, 255, 255, 1.0); 113 | //gl.enable(gl.DEPTH_TEST); 114 | gl.disable(gl.DEPTH_TEST); //此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 115 | gl.depthFunc(gl.LEQUAL); 116 | 117 | gl.enable(gl.CULL_FACE); //一定要启用裁剪,否则显示不出立体感 118 | gl.frontFace(gl.CCW); 119 | gl.cullFace(gl.BACK); //裁剪掉背面 120 | 121 | //gl.enable(gl.TEXTURE_2D);//WebGL: INVALID_ENUM: enable: invalid capability 122 | }; 123 | 124 | WebGLRenderer.prototype = { 125 | constructor: WebGLRenderer, 126 | 127 | render: function(scene, camera) { 128 | if (!(scene instanceof Scene)) { 129 | throw "invalid scene: not World.Scene"; 130 | } 131 | if (!(camera instanceof PerspectiveCamera)) { 132 | throw "invalid camera: not World.PerspectiveCamera"; 133 | } 134 | gl.viewport(0, 0, Kernel.canvas.width, Kernel.canvas.height); 135 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 136 | camera.viewMatrix = null; 137 | camera.viewMatrix = camera.getViewMatrix(); 138 | scene.draw(camera); 139 | }, 140 | 141 | bindScene: function(scene) { 142 | if (!(scene instanceof Scene)) { 143 | throw "invalid scene: not World.Scene"; 144 | } 145 | this.scene = scene; 146 | }, 147 | 148 | bindCamera: function(camera) { 149 | if (!(camera instanceof PerspectiveCamera)) { 150 | throw "invalid camera: not World.PerspectiveCamera"; 151 | } 152 | this.camera = camera; 153 | }, 154 | 155 | tick: function() { 156 | if (Kernel.renderer instanceof WebGLRenderer) { 157 | if (Kernel.renderer.scene && Kernel.renderer.camera) { 158 | Kernel.renderer.render(Kernel.renderer.scene, Kernel.renderer.camera); 159 | } 160 | 161 | if (Kernel.renderer.bAutoRefresh) { 162 | window.requestAnimationFrame(Kernel.renderer.tick); 163 | } 164 | } 165 | }, 166 | 167 | setIfAutoRefresh: function(bAuto) { 168 | if (!Utils.isBool(bAuto)) { 169 | throw "invalid bAuto: not bool"; 170 | } 171 | this.bAutoRefresh = bAuto; 172 | if (this.bAutoRefresh) { 173 | this.tick(); 174 | } 175 | } 176 | }; 177 | return WebGLRenderer; 178 | }); -------------------------------------------------------------------------------- /proxy.jsp: -------------------------------------------------------------------------------- 1 | <%@page session="false"%> 2 | <%@page import="java.net.*,java.io.*"%> 3 | <% 4 | try { 5 | String reqUrl = request.getQueryString(); 6 | URL url = new URL(reqUrl); 7 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); 8 | con.setDoOutput(true); 9 | con.setRequestMethod(request.getMethod()); 10 | if (request.getContentType() != null) { 11 | con.setRequestProperty("Content-Type",request.getContentType()); 12 | } 13 | con.setRequestProperty("Referer", request.getHeader("Referer")); 14 | int clength = request.getContentLength(); 15 | if (clength > 0) { 16 | con.setDoInput(true); 17 | InputStream istream = request.getInputStream(); 18 | OutputStream os = con.getOutputStream(); 19 | final int length = 5000; 20 | byte[] bytes = new byte[length]; 21 | int bytesRead = 0; 22 | while ((bytesRead = istream.read(bytes, 0, length)) > 0) { 23 | os.write(bytes, 0, bytesRead); 24 | } 25 | } else { 26 | con.setRequestMethod("GET"); 27 | } 28 | out.clear(); 29 | out = pageContext.pushBody(); 30 | OutputStream ostream = response.getOutputStream(); 31 | response.setContentType(con.getContentType()); 32 | InputStream in = con.getInputStream(); 33 | final int length = 5000; 34 | byte[] bytes = new byte[length]; 35 | int bytesRead = 0; 36 | while ((bytesRead = in.read(bytes, 0, length)) > 0) { 37 | ostream.write(bytes, 0, bytesRead); 38 | } 39 | } catch (Exception e) { 40 | response.setStatus(500); 41 | } 42 | %> 43 | -------------------------------------------------------------------------------- /require.js: -------------------------------------------------------------------------------- 1 | /** vim: et:ts=4:sw=4:sts=4 2 | * @license RequireJS 2.3.2 Copyright jQuery Foundation and other contributors. 3 | * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE 4 | */ 5 | var requirejs,require,define;!function(global,setTimeout){function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){if(e){var i;for(i=0;i-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,r){return t&&eachProp(t,function(t,n){!i&&hasProp(e,n)||(!r||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[n]=t:(e[n]||(e[n]={}),mixin(e[n],t,i,r)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}function newContext(e){function t(e){var t,i;for(t=0;t0&&(e.splice(t-1,2),t-=2)}}function i(e,i,r){var n,o,a,s,u,c,d,p,f,l,h,m,g=i&&i.split("/"),v=y.map,x=v&&v["*"];if(e&&(e=e.split("/"),d=e.length-1,y.nodeIdCompat&&jsSuffixRegExp.test(e[d])&&(e[d]=e[d].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&g&&(m=g.slice(0,g.length-1),e=m.concat(e)),t(e),e=e.join("/")),r&&v&&(g||x)){a=e.split("/");e:for(s=a.length;s>0;s-=1){if(c=a.slice(0,s).join("/"),g)for(u=g.length;u>0;u-=1)if(o=getOwn(v,g.slice(0,u).join("/")),o&&(o=getOwn(o,c))){p=o,f=s;break e}!l&&x&&getOwn(x,c)&&(l=getOwn(x,c),h=s)}!p&&l&&(p=l,f=h),p&&(a.splice(0,f,p),e=a.join("/"))}return n=getOwn(y.pkgs,e),n?n:e}function r(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===q.contextName)return t.parentNode.removeChild(t),!0})}function n(e){var t=getOwn(y.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),q.require.undef(e),q.makeRequire(null,{skipMap:!0})([e]),!0}function o(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function a(e,t,r,n){var a,s,u,c,d=null,p=t?t.name:null,f=e,l=!0,h="";return e||(l=!1,e="_@r"+(T+=1)),c=o(e),d=c[0],e=c[1],d&&(d=i(d,p,n),s=getOwn(j,d)),e&&(d?h=s&&s.normalize?s.normalize(e,function(e){return i(e,p,n)}):e.indexOf("!")===-1?i(e,p,n):e:(h=i(e,p,n),c=o(h),d=c[0],h=c[1],r=!0,a=q.nameToUrl(h))),u=!d||s||r?"":"_unnormalized"+(A+=1),{prefix:d,name:h,parentMap:t,unnormalized:!!u,url:a,originalName:f,isDefine:l,id:(d?d+"!"+h:h)+u}}function s(e){var t=e.id,i=getOwn(S,t);return i||(i=S[t]=new q.Module(e)),i}function u(e,t,i){var r=e.id,n=getOwn(S,r);!hasProp(j,r)||n&&!n.defineEmitComplete?(n=s(e),n.error&&"error"===t?i(n.error):n.on(t,i)):"defined"===t&&i(j[r])}function c(e,t){var i=e.requireModules,r=!1;t?t(e):(each(i,function(t){var i=getOwn(S,t);i&&(i.error=e,i.events.error&&(r=!0,i.emit("error",e)))}),r||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(q.defQueueMap[t]=!0),O.push(e)}),globalDefQueue=[])}function p(e){delete S[e],delete k[e]}function f(e,t,i){var r=e.map.id;e.error?e.emit("error",e.error):(t[r]=!0,each(e.depMaps,function(r,n){var o=r.id,a=getOwn(S,o);!a||e.depMatched[n]||i[o]||(getOwn(t,o)?(e.defineDep(n,j[o]),e.check()):f(a,t,i))}),i[r]=!0)}function l(){var e,t,i=1e3*y.waitSeconds,o=i&&q.startTime+i<(new Date).getTime(),a=[],s=[],u=!1,d=!0;if(!x){if(x=!0,eachProp(k,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&o)n(c)?(t=!0,u=!0):(a.push(c),r(c));else if(!e.inited&&e.fetched&&i.isDefine&&(u=!0,!i.prefix))return d=!1}),o&&a.length)return e=makeError("timeout","Load timeout for modules: "+a,null,a),e.contextName=q.contextName,c(e);d&&each(s,function(e){f(e,{},{})}),o&&!t||!u||!isBrowser&&!isWebWorker||w||(w=setTimeout(function(){w=0,l()},50)),x=!1}}function h(e){hasProp(j,e[0])||s(a(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,q.onScriptLoad,"load","onreadystatechange"),m(t,q.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();O.length;){if(e=O.shift(),null===e[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));h(e)}q.defQueueMap={}}var x,b,q,E,w,y={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},S={},k={},M={},O=[],j={},P={},R={},T=1,A=1;return E={require:function(e){return e.require?e.require:e.require=q.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?j[e.map.id]=e.exports:e.exports=j[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(y.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},b=function(e){this.events=getOwn(M,e.id)||{},this.map=e,this.shim=getOwn(y.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},b.prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,q.startTime=(new Date).getTime();var e=this.map;return this.shim?void q.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()})):e.prefix?this.callPlugin():this.load()}},load:function(){var e=this.map.url;P[e]||(P[e]=!0,q.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=q.execCb(i,o,r,n)}catch(t){e=t}else n=q.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&(t=this.module,t?n=t.exports:this.usingExports&&(n=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(j[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(q,this.map,a)}p(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(q.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,r=a(e.prefix);this.depMaps.push(r),u(r,"defined",bind(this,function(r){var n,o,d,f=getOwn(R,this.map.id),l=this.map.name,h=this.map.parentMap?this.map.parentMap.name:null,m=q.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(r.normalize&&(l=r.normalize(l,function(e){return i(e,h,!0)})||""),o=a(e.prefix+"!"+l,this.map.parentMap),u(o,"defined",bind(this,function(e){this.map.normalizedMap=o,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),d=getOwn(S,o.id),void(d&&(this.depMaps.push(o),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):f?(this.map.url=q.nameToUrl(f),void this.load()):(n=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})}),n.error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(S,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&p(e.map.id)}),c(e)}),n.fromText=bind(this,function(i,r){var o=e.name,u=a(o),d=useInteractive;r&&(i=r),d&&(useInteractive=!1),s(u),hasProp(y.config,t)&&(y.config[o]=y.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(u),q.completeLoad(o),m([o],n)}),void r.load(e.name,m,n,y))})),q.enable(r,this),this.pluginMaps[r.id]=r},enable:function(){k[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=a(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(E,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,u(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?u(e,"error",bind(this,this.errback)):this.events.error&&u(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=S[i],hasProp(E,i)||!r||r.enabled||q.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(S,e.id);t&&!t.enabled&&q.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},q={config:y,contextName:e,registry:S,defined:j,urlFetched:P,defQueue:O,defQueueMap:{},Module:b,makeModuleMap:a,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(i.indexOf("?")===-1?"?":"&")+t}}var i=y.shim,r={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){r[t]?(y[t]||(y[t]={}),mixin(y[t],e,!0,!0)):y[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(R[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=q.makeShimExports(e)),i[t]=e}),y.shim=i),e.packages&&each(e.packages,function(e){var t,i;e="string"==typeof e?{name:e}:e,i=e.name,t=e.location,t&&(y.paths[i]=e.location),y.pkgs[i]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(S,function(e,t){e.inited||e.map.unnormalized||(e.map=a(t,null,!0))}),(e.deps||e.callback)&&q.require(e.deps||[],e.callback)},makeShimExports:function(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t},makeRequire:function(t,n){function o(i,r,u){var d,p,f;return n.enableBuildCallback&&r&&isFunction(r)&&(r.__requireJsBuild=!0),"string"==typeof i?isFunction(r)?c(makeError("requireargs","Invalid require call"),u):t&&hasProp(E,i)?E[i](S[t.id]):req.get?req.get(q,i,t,o):(p=a(i,t,!1,!0),d=p.id,hasProp(j,d)?j[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),q.nextTick(function(){v(),f=s(a(null,t)),f.skipMap=n.skipMap,f.init(i,r,u,{enabled:!0}),l()}),o)}return n=n||{},mixin(o,{isBrowser:isBrowser,toUrl:function(e){var r,n=e.lastIndexOf("."),o=e.split("/")[0],a="."===o||".."===o;return n!==-1&&(!a||n>1)&&(r=e.substring(n,e.length),e=e.substring(0,n)),q.nameToUrl(i(e,t&&t.id,!0),r,!0)},defined:function(e){return hasProp(j,a(e,t,!1,!0).id)},specified:function(e){return e=a(e,t,!1,!0).id,hasProp(j,e)||hasProp(S,e)}}),t||(o.undef=function(e){d();var i=a(e,t,!0),n=getOwn(S,e);n.undefed=!0,r(e),delete j[e],delete P[i.url],delete M[e],eachReverse(O,function(t,i){t[0]===e&&O.splice(i,1)}),delete q.defQueueMap[e],n&&(n.events.defined&&(M[e]=n.events),p(e))}),o},enable:function(e){var t=getOwn(S,e.id);t&&s(e).enable()},completeLoad:function(e){var t,i,r,o=getOwn(y.shim,e)||{},a=o.exports;for(d();O.length;){if(i=O.shift(),null===i[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);h(i)}if(q.defQueueMap={},r=getOwn(S,e),!t&&!hasProp(j,e)&&r&&!r.inited){if(!(!y.enforceDefine||a&&getGlobal(a)))return n(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));h([e,o.deps||[],o.exportsFn])}l()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c,d=getOwn(y.pkgs,e);if(d&&(e=d),c=getOwn(R,e))return q.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(r=y.paths,n=e.split("/"),o=n.length;o>0;o-=1)if(a=n.slice(0,o).join("/"),u=getOwn(r,a)){isArray(u)&&(u=u[0]),n.splice(0,o,u);break}s=n.join("/"),s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js"),s=("/"===s.charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":y.baseUrl)+s}return y.urlArgs&&!/^blob\:/.test(s)?s+y.urlArgs(e,s):s},load:function(e,t){req.load(q,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);q.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!n(t.id)){var i=[];return eachProp(S,function(e,r){0!==r.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(r),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},q.require=q.makeRequire(),q}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.2",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if("undefined"==typeof define){if("undefined"!=typeof requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}"undefined"==typeof require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),n=getOwn(contexts,a),n||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick="undefined"!=typeof setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],baseElement=document.getElementsByTagName("base")[0],baseElement&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(e,t,i){var r,n=e&&e.config||{};if(isBrowser)return r=req.createNode(n,t,i),r.setAttribute("data-requirecontext",e.contextName),r.setAttribute("data-requiremodule",t),!r.attachEvent||r.attachEvent.toString&&r.attachEvent.toString().indexOf("[native code")<0||isOpera?(r.addEventListener("load",e.onScriptLoad,!1),r.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,r.attachEvent("onreadystatechange",e.onScriptLoad)),r.src=i,n.onNodeCreated&&n.onNodeCreated(r,n,t,i),currentlyAddingScript=r,baseElement?head.insertBefore(r,baseElement):head.appendChild(r),currentlyAddingScript=null,r;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(r){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,r,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||mainScript.indexOf("!")!==-1||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var r,n;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript(),r&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")])),n?(n.defQueue.push([e,t,i]),n.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,"undefined"==typeof setTimeout?void 0:setTimeout); -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wandergis/WebGlobe/06bef518d3b0c6f702922e8a22c2e2b90ae102bb/screenshot.png -------------------------------------------------------------------------------- /ts/world/Enum.ts: -------------------------------------------------------------------------------- 1 | enum Enum { 2 | UNKNOWN, 3 | FULL_IN, 4 | FULL_OUT, 5 | IN_OUT, 6 | NOKIA_TILED_MAP, 7 | Google_TILED_MAP, 8 | OSM_TILED_MAP, 9 | BLENDED_TILED_MAP, 10 | GLOBE_TILE, 11 | TERRAIN_TILE 12 | } 13 | 14 | export default Enum; -------------------------------------------------------------------------------- /ts/world/Kernel.ts: -------------------------------------------------------------------------------- 1 | const kernel = { 2 | gl: null, 3 | canvas: null, 4 | renderer: null, 5 | globe: null, 6 | idCounter: 0, //Object3D对象的唯一标识 7 | BASE_LEVEL: 6, //渲染的基准层级 8 | EARTH_RADIUS: 6378137, 9 | MAX_PROJECTED_COORD: 20037508.3427892, 10 | ELEVATION_LEVEL: 7, //开始获取高程数据 11 | TERRAIN_LEVEL: 10, //开始显示三维地形 12 | TERRAIN_ENABLED: true, //是否启用三维地形 13 | TERRAIN_PITCH: 80, //开始显示三维地形的pich 14 | proxy: "" 15 | }; 16 | export default kernel; -------------------------------------------------------------------------------- /ts/world/Math.ts: -------------------------------------------------------------------------------- 1 | import Utils from "./Utils"; 2 | 3 | const math = { 4 | ONE_RADIAN_EQUAL_DEGREE:57.29577951308232,//180/Math.PI 5 | ONE_DEGREE_EQUAL_RADIAN:0.017453292519943295,//Math.PI/180 6 | LEFT_TOP:"LEFT_TOP", 7 | RIGHT_TOP:"RIGHT_TOP", 8 | LEFT_BOTTOM:"LEFT_BOTTOM", 9 | RIGHT_BOTTOM:"RIGHT_BOTTOM", 10 | LEFT:"LEFT", 11 | RIGHT:"RIGHT", 12 | TOP:"TOP", 13 | BOTTOM:"BOTTOM", 14 | 15 | izZero(value: any) : boolean { 16 | if(!Utils.isNumber(value)){ 17 | throw "invalid value"; 18 | } 19 | return Math.abs(value) < 0.000001; 20 | }, 21 | 22 | /** 23 | * 将其他进制的数字转换为10进制 24 | * @param numSys 要准换的进制 25 | * @param strNum 字符串形式的要转换的数据 26 | * @returns {number} 整数的十进制数据 27 | */ 28 | numerationSystemTo10(numSys: number, strNum: string) : number{ 29 | if(!Utils.isPositiveInteger(numSys)){ 30 | throw "invalid numSys"; 31 | } 32 | if(!Utils.isString(strNum)){ 33 | throw "invalid strNum"; 34 | } 35 | var sum = 0; 36 | for(var i=0;i 0){ 313 | var sqrtDelta = Math.sqrt(delta); 314 | var k1 = (-2*t+sqrtDelta)/(2*A); 315 | var x1 = k1*a+x0; 316 | var y1 = k1*b+y0; 317 | var z1 = k1*c+z0; 318 | var p1 = new World.Vertice(x1,y1,z1); 319 | result.push(p1); 320 | 321 | var k2 = (-2*t-sqrtDelta)/(2*A); 322 | var x2 = k2*a+x0; 323 | var y2 = k2*b+y0; 324 | var z2 = k2*c+z0; 325 | var p2 = new World.Vertice(x2,y2,z2); 326 | result.push(p2); 327 | } 328 | } 329 | 330 | return result; 331 | }, 332 | 333 | /** 334 | * 计算过P点且垂直于向量V的平面 335 | * @param vertice P点 336 | * @param direction 向量V 337 | * @return {Object} World.Plan 返回平面表达式中Ax+By+Cz+D=0的A、B、C、D的信息 338 | */ 339 | getCrossPlaneByLine : function(vertice,direction){ 340 | if(!(vertice instanceof World.Vertice)){ 341 | throw "invalid vertice"; 342 | } 343 | if(!(direction instanceof World.Vector)){ 344 | throw "invalid direction"; 345 | } 346 | var verticeCopy = vertice.getCopy(); 347 | var directionCopy = direction.getCopy(); 348 | directionCopy.normalize(); 349 | var a = directionCopy.x; 350 | var b = directionCopy.y; 351 | var c = directionCopy.z; 352 | var x0 = verticeCopy.x; 353 | var y0 = verticeCopy.y; 354 | var z0 = verticeCopy.z; 355 | var d = -(a*x0+b*y0+c*z0); 356 | var plan = new World.Plan(a,b,c,d); 357 | return plan; 358 | }, 359 | 360 | /////////////////////////////////////////////////////////////////////////////////////////// 361 | //点变换: Canvas->NDC 362 | convertPointFromCanvasToNDC : function(canvasX,canvasY){ 363 | if(!(Utils.isNumber(canvasX))){ 364 | throw "invalid canvasX"; 365 | } 366 | if(!(Utils.isNumber(canvasY))){ 367 | throw "invalid canvasY"; 368 | } 369 | var ndcX = 2 * canvasX / World.canvas.width - 1; 370 | var ndcY = 1 - 2 * canvasY / World.canvas.height; 371 | return [ndcX,ndcY]; 372 | }, 373 | 374 | //点变换: NDC->Canvas 375 | convertPointFromNdcToCanvas : function(ndcX,ndcY){ 376 | if(!(Utils.isNumber(ndcX))){ 377 | throw "invalid ndcX"; 378 | } 379 | if(!(Utils.isNumber(ndcY))){ 380 | throw "invalid ndcY"; 381 | } 382 | var canvasX = (1 + ndcX) * World.canvas.width / 2.0; 383 | var canvasY = (1 - ndcY) * World.canvas.height / 2.0; 384 | return [canvasX,canvasY]; 385 | }, 386 | 387 | /** 388 | * 根据层级计算出摄像机应该放置到距离地球表面多远的位置 389 | * @param level 390 | * @return {*} 391 | */ 392 | getLengthFromCamera2EarthSurface : function(level){ 393 | if(!(World.Utils.isNonNegativeInteger(level))){ 394 | throw "invalid level"; 395 | } 396 | return 7820683/Math.pow(2,level); 397 | }, 398 | 399 | /**将经纬度转换为笛卡尔空间直角坐标系中的x、y、z 400 | * @lon 经度(角度单位) 401 | * @lat 纬度(角度单位) 402 | * @r optional 可选的地球半径 403 | * @p 笛卡尔坐标系中的坐标 404 | */ 405 | geographicToCartesianCoord : function(lon,lat,r){ 406 | if(!(Utils.isNumber(lon) && lon >= -(180+0.001) && lon <= (180+0.001))){ 407 | throw "invalid lon"; 408 | } 409 | if(!(Utils.isNumber(lat) && lat >= -(90+0.001) && lat <= (90+0.001))){ 410 | throw "invalid lat"; 411 | } 412 | r = r||World.EARTH_RADIUS; 413 | var radianLon = this.degreeToRadian(lon); 414 | var radianLat = this.degreeToRadian(lat); 415 | var sin1 = Math.sin(radianLon); 416 | var cos1 = Math.cos(radianLon); 417 | var sin2 = Math.sin(radianLat); 418 | var cos2 = Math.cos(radianLat); 419 | var x = r*sin1*cos2; 420 | var y = r*sin2; 421 | var z = r*cos1*cos2; 422 | return new World.Vertice(x,y,z); 423 | }, 424 | 425 | /** 426 | * 将笛卡尔空间直角坐标系中的坐标转换为经纬度,以角度表示 427 | * @param vertice 428 | * @return {Array} 429 | */ 430 | cartesianCoordToGeographic : function(vertice){ 431 | if(!(vertice instanceof World.Vertice)){ 432 | throw "invalid vertice"; 433 | } 434 | var verticeCopy = vertice.getCopy(); 435 | var x = verticeCopy.x; 436 | var y = verticeCopy.y; 437 | var z = verticeCopy.z; 438 | var sin2 = y/World.EARTH_RADIUS; 439 | if(sin2 > 1){ 440 | sin2 = 2; 441 | } 442 | else if(sin2 < -1){ 443 | sin2 = -1; 444 | } 445 | var radianLat = Math.asin(sin2); 446 | var cos2 = Math.cos(radianLat); 447 | var sin1 = x / (World.EARTH_RADIUS * cos2); 448 | if(sin1 > 1){ 449 | sin1 = 1; 450 | } 451 | else if(sin1 < -1){ 452 | sin1 = -1; 453 | } 454 | var cos1 = z / (World.EARTH_RADIUS * cos2); 455 | if(cos1 > 1){ 456 | cos1 = 1; 457 | } 458 | else if(cos1 < -1){ 459 | cos1 = -1; 460 | } 461 | var radianLog = Math.asin(sin1); 462 | if(sin1 >= 0){//经度在[0,π] 463 | if(cos1 >= 0){//经度在[0,π/2]之间 464 | radianLog = radianLog; 465 | } 466 | else{//经度在[π/2,π]之间 467 | radianLog = Math.PI - radianLog; 468 | } 469 | } 470 | else{//经度在[-π,0]之间 471 | if(cos1 >= 0){//经度在[-π/2,0]之间 472 | radianLog = radianLog; 473 | } 474 | else{//经度在[-π,-π/2]之间 475 | radianLog = -radianLog - Math.PI; 476 | } 477 | } 478 | var degreeLat = math.radianToDegree(radianLat); 479 | var degreeLog = math.radianToDegree(radianLog); 480 | return [degreeLog,degreeLat]; 481 | }, 482 | 483 | /** 484 | * 根据tile在父tile中的位置获取该tile的行列号等信息 485 | * @param parentLevel 父tile的层级 486 | * @param parentRow 父tile的行号 487 | * @param parentColumn 父tile的列号 488 | * @param position tile在父tile中的位置 489 | * @return {Object} 490 | */ 491 | getTileGridByParent : function(parentLevel,parentRow,parentColumn,position){ 492 | if(!Utils.isNonNegativeInteger(parentLevel)){ 493 | throw "invalid parentLevel"; 494 | } 495 | if(!Utils.isNonNegativeInteger(parentRow)){ 496 | throw "invalid parentRow"; 497 | } 498 | if(!Utils.isNonNegativeInteger(parentColumn)){ 499 | throw "invalid parentColumn"; 500 | } 501 | var level = parentLevel + 1; 502 | var row = -1; 503 | var column = -1; 504 | if(position == this.LEFT_TOP){ 505 | row = 2 * parentRow; 506 | column = 2 * parentColumn; 507 | } 508 | else if(position == this.RIGHT_TOP){ 509 | row = 2 * parentRow; 510 | column = 2 * parentColumn + 1; 511 | } 512 | else if(position == this.LEFT_BOTTOM){ 513 | row = 2 * parentRow + 1; 514 | column = 2 * parentColumn; 515 | } 516 | else if(position == this.RIGHT_BOTTOM){ 517 | row = 2 * parentRow + 1; 518 | column = 2 * parentColumn + 1; 519 | } 520 | else{ 521 | throw "invalid position"; 522 | } 523 | return new World.TileGrid(level,row,column); 524 | }, 525 | 526 | //返回切片在直接付切片中的位置 527 | getTilePositionOfParent : function(level,row,column,/*optional*/ parent){ 528 | if(!World.Utils.isNonNegativeInteger(level)){ 529 | throw "invalid level"; 530 | } 531 | if(!World.Utils.isNonNegativeInteger(row)){ 532 | throw "invalid row"; 533 | } 534 | if(!World.Utils.isNonNegativeInteger(column)){ 535 | throw "invalid column"; 536 | } 537 | var position = "UNKNOWN"; 538 | parent = parent||this.getTileGridAncestor(level-1,level,row,column); 539 | var ltTileGrid = this.getTileGridByParent(parent.level,parent.row,parent.column,this.LEFT_TOP); 540 | if(ltTileGrid.row == row){ 541 | //上面那一行 542 | if(ltTileGrid.column == column){ 543 | //处于左上角 544 | position = this.LEFT_TOP; 545 | } 546 | else if(ltTileGrid.column+1 == column){ 547 | //处于右上角 548 | position = this.RIGHT_TOP; 549 | } 550 | } 551 | else if(ltTileGrid.row+1 == row){ 552 | //下面那一行 553 | if(ltTileGrid.column == column){ 554 | //处于左下角 555 | position = this.LEFT_BOTTOM; 556 | } 557 | else if(ltTileGrid.column+1 == column){ 558 | //处于右下角 559 | position = this.RIGHT_BOTTOM; 560 | } 561 | } 562 | return position; 563 | }, 564 | 565 | //获取在某一level周边position的切片 566 | getTileGridByBrother : function(brotherLevel,brotherRow,brotherColumn,position,options){ 567 | if(!(World.Utils.isNonNegativeInteger(brotherLevel))){ 568 | throw "invalid brotherLevel"; 569 | } 570 | if(!(World.Utils.isNonNegativeInteger(brotherRow))){ 571 | throw "invalid brotherRow"; 572 | } 573 | if(!(World.Utils.isNonNegativeInteger(brotherColumn))){ 574 | throw "invalid brotherColumn"; 575 | } 576 | 577 | options = options||{}; 578 | var result = new World.TileGrid(brotherLevel,brotherRow,brotherColumn); 579 | 580 | //TODO maxSize可优化 该level下row/column的最大数量 581 | if(position == this.LEFT){ 582 | if(brotherColumn == 0){ 583 | var maxSize = options.maxSize || Math.pow(2,brotherLevel); 584 | result.column = maxSize - 1; 585 | } 586 | else{ 587 | result.column = brotherColumn - 1; 588 | } 589 | } 590 | else if(position == this.RIGHT){ 591 | var maxSize = options.maxSize || Math.pow(2,brotherLevel); 592 | if(brotherColumn == maxSize - 1){ 593 | result.column = 0; 594 | } 595 | else{ 596 | result.column = brotherColumn + 1; 597 | } 598 | } 599 | else if(position == this.TOP){ 600 | if(brotherRow == 0){ 601 | var maxSize = options.maxSize || Math.pow(2,brotherLevel); 602 | result.row = maxSize - 1; 603 | } 604 | else{ 605 | result.row = brotherRow - 1; 606 | } 607 | } 608 | else if(position == this.BOTTOM){ 609 | var maxSize = options.maxSize || Math.pow(2,brotherLevel); 610 | if(brotherRow == maxSize - 1){ 611 | result.row = 0; 612 | } 613 | else{ 614 | result.row = brotherRow + 1; 615 | } 616 | } 617 | else{ 618 | throw "invalid position"; 619 | } 620 | return result; 621 | }, 622 | 623 | /** 624 | * 获取切片的祖先切片, 625 | * @param ancestorLevel 祖先切片的level 626 | * @param level 当前切片level 627 | * @param row 当前切片row 628 | * @param column 当前切片column 629 | * @returns {null} 630 | */ 631 | getTileGridAncestor : function(ancestorLevel,level,row,column){ 632 | if(!Utils.isNonNegativeInteger(ancestorLevel)){ 633 | throw "invalid ancestorLevel"; 634 | } 635 | if(!Utils.isNonNegativeInteger(level)){ 636 | throw "invalid level"; 637 | } 638 | if(!Utils.isNonNegativeInteger(row)){ 639 | throw "invalid row"; 640 | } 641 | if(!Utils.isNonNegativeInteger(column)){ 642 | throw "invalid column"; 643 | } 644 | var result = null; 645 | if(ancestorLevel < level){ 646 | var deltaLevel = level - ancestorLevel; 647 | //ancestor能够包含a*a个当前切片 648 | var a = Math.pow(2,deltaLevel); 649 | var ancestorRow = Math.floor(row/a); 650 | var ancestorColumn = Math.floor(column/a); 651 | result = new World.TileGrid(ancestorLevel,ancestorRow,ancestorColumn); 652 | } 653 | else if(ancestorLevel == level){ 654 | result = new World.TileGrid(level,row,column); 655 | } 656 | return result; 657 | }, 658 | 659 | getTileGridByGeo : function(lon,lat,level){ 660 | if(!(World.Utils.isNumber(lon) && lon >= -180 && lon <= 180)){ 661 | throw "invalid lon"; 662 | } 663 | if(!(World.Utils.isNumber(lat) && lat >= -90 && lat <= 90)){ 664 | throw "invalid lat"; 665 | } 666 | if(!World.Utils.isNonNegativeInteger(level)){ 667 | throw "invalid level"; 668 | } 669 | var coordWebMercator = this.degreeGeographicToWebMercator(lon,lat); 670 | var x = coordWebMercator[0]; 671 | var y = coordWebMercator[1]; 672 | var horX = x + World.MAX_PROJECTED_COORD; 673 | var verY = World.MAX_PROJECTED_COORD - y; 674 | var size = World.MAX_PROJECTED_COORD / Math.pow(2,level-1); 675 | var row = Math.floor(verY / size); 676 | var column = Math.floor(horX / size); 677 | return new World.TileGrid(level,row,column); 678 | }, 679 | 680 | /** 681 | * 角度转弧度 682 | * @param degree 683 | * @return {*} 684 | */ 685 | degreeToRadian : function(degree){ 686 | if(!(World.Utils.isNumber(degree))){ 687 | throw "invalid degree"; 688 | } 689 | return degree*this.ONE_DEGREE_EQUAL_RADIAN; 690 | }, 691 | 692 | /** 693 | * 弧度转角度 694 | * @param radian 695 | * @return {*} 696 | */ 697 | radianToDegree : function(radian){ 698 | if(!(Utils.isNumber(radian))){ 699 | throw "invalid radian"; 700 | } 701 | return radian*this.ONE_RADIAN_EQUAL_DEGREE; 702 | }, 703 | 704 | /** 705 | * 将投影坐标x转换为以弧度表示的经度 706 | * @param x 投影坐标x 707 | * @return {Number} 返回的经度信息以弧度表示 708 | */ 709 | webMercatorXToRadianLog : function(x){ 710 | if(!(Utils.isNumber(x))){ 711 | throw "invalid x"; 712 | } 713 | return x / World.EARTH_RADIUS; 714 | }, 715 | 716 | /** 717 | * 将投影坐标x转换为以角度表示的经度 718 | * @param x 投影坐标x 719 | * @return {*} 返回的经度信息以角度表示 720 | */ 721 | webMercatorXToDegreeLog : function(x){ 722 | if(!(Utils.isNumber(x))){ 723 | throw "invalid x"; 724 | } 725 | var radianLog = this.webMercatorXToRadianLog(x); 726 | return this.radianToDegree(radianLog); 727 | }, 728 | 729 | /** 730 | * 将投影坐标y转换为以弧度表示的纬度 731 | * @param y 投影坐标y 732 | * @return {Number} 返回的纬度信息以弧度表示 733 | */ 734 | webMercatorYToRadianLat : function(y){ 735 | if(!(Utils.isNumber(y))){ 736 | throw "invalid y"; 737 | } 738 | var a = y / World.EARTH_RADIUS; 739 | var b = Math.pow(Math.E,a); 740 | var c = Math.atan(b); 741 | var radianLat = 2*c - Math.PI/2; 742 | return radianLat; 743 | }, 744 | 745 | /** 746 | * 将投影坐标y转换为以角度表示的纬度 747 | * @param y 投影坐标y 748 | * @return {*} 返回的纬度信息以角度表示 749 | */ 750 | webMercatorYToDegreeLat : function(y){ 751 | if(!(Utils.isNumber(y))){ 752 | throw "invalid y"; 753 | } 754 | var radianLat = this.webMercatorYToRadianLat(y); 755 | return this.radianToDegree(radianLat); 756 | }, 757 | 758 | /** 759 | * 将投影坐标x、y转换成以弧度表示的经纬度 760 | * @param x 投影坐标x 761 | * @param y 投影坐标y 762 | * @return {Array} 返回的经纬度信息以弧度表示 763 | */ 764 | webMercatorToRadianGeographic : function(x,y){ 765 | var radianLog = this.webMercatorXToRadianLog(x); 766 | var radianLat = this.webMercatorYToRadianLat(y); 767 | return [radianLog,radianLat]; 768 | }, 769 | 770 | /** 771 | * 将投影坐标x、y转换成以角度表示的经纬度 772 | * @param x 投影坐标x 773 | * @param y 投影坐标y 774 | * @return {Array} 返回的经纬度信息以角度表示 775 | */ 776 | webMercatorToDegreeGeographic : function(x,y){ 777 | var degreeLog = this.webMercatorXToDegreeLog(x); 778 | var degreeLat = this.webMercatorYToDegreeLat(y); 779 | return [degreeLog,degreeLat]; 780 | }, 781 | 782 | /** 783 | * 将以弧度表示的经度转换为投影坐标x 784 | * @param radianLog 以弧度表示的经度 785 | * @return {*} 投影坐标x 786 | */ 787 | radianLogToWebMercatorX : function(radianLog){ 788 | if(!(Utils.isNumber(radianLog) && radianLog <= (Math.PI+0.001) && radianLog >= -(Math.PI+0.001))){ 789 | throw "invalid radianLog"; 790 | } 791 | return World.EARTH_RADIUS * radianLog; 792 | }, 793 | 794 | /** 795 | * 将以角度表示的纬度转换为投影坐标y 796 | * @param degreeLog 以角度表示的经度 797 | * @return {*} 投影坐标x 798 | */ 799 | degreeLogToWebMercatorX : function(degreeLog){ 800 | if(!(Utils.isNumber(degreeLog) && degreeLog <= (180+0.001) && degreeLog >= -(180+0.001))){ 801 | throw "invalid degreeLog"; 802 | } 803 | var radianLog = this.degreeToRadian(degreeLog); 804 | return this.radianLogToWebMercatorX(radianLog); 805 | }, 806 | 807 | /** 808 | * 将以弧度表示的纬度转换为投影坐标y 809 | * @param radianLat 以弧度表示的纬度 810 | * @return {Number} 投影坐标y 811 | */ 812 | radianLatToWebMercatorY : function(radianLat){ 813 | if(!(Utils.isNumber(radianLat) && radianLat <= (Math.PI/2+0.001) && radianLat >= -(Math.PI/2+0.001))){ 814 | throw "invalid radianLat"; 815 | } 816 | var a = Math.PI/4 + radianLat/2; 817 | var b = Math.tan(a); 818 | var c = Math.log(b); 819 | var y = World.EARTH_RADIUS * c; 820 | return y; 821 | }, 822 | 823 | /** 824 | * 将以角度表示的纬度转换为投影坐标y 825 | * @param degreeLat 以角度表示的纬度 826 | * @return {Number} 投影坐标y 827 | */ 828 | degreeLatToWebMercatorY : function(degreeLat){ 829 | if(!(Utils.isNumber(degreeLat) && degreeLat <= (90+0.001) && degreeLat >= -(90+0.001))){ 830 | throw "invalid degreeLat"; 831 | } 832 | var radianLat = this.degreeToRadian(degreeLat); 833 | return this.radianLatToWebMercatorY(radianLat); 834 | }, 835 | 836 | /** 837 | * 将以弧度表示的经纬度转换为投影坐标 838 | * @param radianLog 以弧度表示的经度 839 | * @param radianLat 以弧度表示的纬度 840 | * @return {Array} 投影坐标x、y 841 | */ 842 | radianGeographicToWebMercator : function(radianLog,radianLat){ 843 | var x = this.radianLogToWebMercatorX(radianLog); 844 | var y = this.radianLatToWebMercatorY(radianLat); 845 | return [x,y]; 846 | }, 847 | 848 | /** 849 | * 将以角度表示的经纬度转换为投影坐标 850 | * @param degreeLog 以角度表示的经度 851 | * @param degreeLat 以角度表示的纬度 852 | * @return {Array} 853 | */ 854 | degreeGeographicToWebMercator : function(degreeLog,degreeLat){ 855 | var x = this.degreeLogToWebMercatorX(degreeLog); 856 | var y = this.degreeLatToWebMercatorY(degreeLat); 857 | return [x,y]; 858 | }, 859 | 860 | //根据切片的level、row、column计算该切片所覆盖的投影区域的范围 861 | getTileWebMercatorEnvelopeByGrid : function(level,row,column){ 862 | if(!(Utils.isNonNegativeInteger(level))){ 863 | throw "invalid level"; 864 | } 865 | if(!(Utils.isNonNegativeInteger(row))){ 866 | throw "invalid row"; 867 | } 868 | if(!(Utils.isNonNegativeInteger(column))){ 869 | throw "invalid column"; 870 | } 871 | var k = World.MAX_PROJECTED_COORD; 872 | var size = 2*k / Math.pow(2,level); 873 | var minX = -k + column * size; 874 | var maxX = minX + size; 875 | var maxY = k - row * size; 876 | var minY = maxY - size; 877 | var Eproj = { 878 | "minX":minX, 879 | "minY":minY, 880 | "maxX":maxX, 881 | "maxY":maxY 882 | }; 883 | return Eproj; 884 | }, 885 | 886 | //根据切片的level、row、column计算该切片所覆盖的经纬度区域的范围,以经纬度表示返回结果 887 | getTileGeographicEnvelopByGrid : function(level,row,column){ 888 | if(!(Utils.isNonNegativeInteger(level))){ 889 | throw "invalid level"; 890 | } 891 | if(!(Utils.isNonNegativeInteger(row))){ 892 | throw "invalid row"; 893 | } 894 | if(!(Utils.isNonNegativeInteger(column))){ 895 | throw "invalid column"; 896 | } 897 | var Eproj = this.getTileWebMercatorEnvelopeByGrid(level,row,column); 898 | var pMin = this.webMercatorToDegreeGeographic(Eproj.minX,Eproj.minY); 899 | var pMax = this.webMercatorToDegreeGeographic(Eproj.maxX,Eproj.maxY); 900 | var Egeo = { 901 | "minLon":pMin[0], 902 | "minLat":pMin[1], 903 | "maxLon":pMax[0], 904 | "maxLat":pMax[1] 905 | }; 906 | return Egeo; 907 | }, 908 | 909 | //根据切片的level、row、column计算该切片所覆盖的笛卡尔空间直角坐标系的范围,以x、y、z表示返回结果 910 | getTileCartesianEnvelopByGrid : function(level,row,column){ 911 | if(!(Utils.isNonNegativeInteger(level))){ 912 | throw "invalid level"; 913 | } 914 | if(!(Utils.isNonNegativeInteger(row))){ 915 | throw "invalid row"; 916 | } 917 | if(!(Utils.isNonNegativeInteger(column))){ 918 | throw "invalid column"; 919 | } 920 | var Egeo = this.getTileGeographicEnvelopByGrid(level,row,column); 921 | var minLon = Egeo.minLon; 922 | var minLat = Egeo.minLat; 923 | var maxLon = Egeo.maxLon; 924 | var maxLat = Egeo.maxLat; 925 | var pLeftBottom = this.geographicToCartesianCoord(minLon,minLat); 926 | var pLeftTop = this.geographicToCartesianCoord(minLon,maxLat); 927 | var pRightTop = this.geographicToCartesianCoord(maxLon,maxLat); 928 | var pRightBottom = this.geographicToCartesianCoord(maxLon,minLat); 929 | var Ecar = { 930 | "pLeftBottom":pLeftBottom, 931 | "pLeftTop":pLeftTop, 932 | "pRightTop":pRightTop, 933 | "pRightBottom":pRightBottom, 934 | "minLon":minLon, 935 | "minLat":minLat, 936 | "maxLon":maxLon, 937 | "maxLat":maxLat 938 | }; 939 | return Ecar; 940 | }, 941 | 942 | /** 943 | * 获取切片的中心点,以经纬度数组形式返回 944 | * @param level 945 | * @param row 946 | * @param column 947 | * @return {Array} 948 | */ 949 | getGeographicTileCenter : function(level,row,column){ 950 | if(!(Utils.isNonNegativeInteger(level))){ 951 | throw "invalid level"; 952 | } 953 | if(!(Utils.isNonNegativeInteger(row))){ 954 | throw "invalid row"; 955 | } 956 | if(!(Utils.isNonNegativeInteger(column))){ 957 | throw "invalid column"; 958 | } 959 | var Egeo = this.getTileGeographicEnvelopByGrid(level,row,column); 960 | var minLon = Egeo.minLon; 961 | var minLat = Egeo.minLat; 962 | var maxLon = Egeo.maxLon; 963 | var maxLat = Egeo.maxLat; 964 | var centerLon = (minLon+maxLon)/2;//切片的经度中心 965 | var centerLat = (minLat+maxLat)/2;//切片的纬度中心 966 | var lonlatTileCenter = [centerLon,centerLat]; 967 | return lonlatTileCenter; 968 | }, 969 | 970 | getCartesianTileCenter : function(level,row,column){ 971 | if(!(Utils.isNonNegativeInteger(level))){ 972 | throw "invalid level"; 973 | } 974 | if(!(Utils.isNonNegativeInteger(row))){ 975 | throw "invalid row"; 976 | } 977 | if(!(Utils.isNonNegativeInteger(column))){ 978 | throw "invalid column"; 979 | } 980 | var lonLat = this.getGeographicTileCenter(level,row,column); 981 | var vertice = this.geographicToCartesianCoord(lonLat[0],lonLat[1]); 982 | return vertice; 983 | }, 984 | 985 | /** 986 | * 计算TRIANGLES的平均法向量 987 | * @param vs 传入的顶点坐标数组 array 988 | * @param ind 传入的顶点的索引数组 array 989 | * @return {Array} 返回每个顶点的平均法向量的数组 990 | */ 991 | calculateNormals : function(vs, ind){ 992 | if(!Utils.isArray(vs)){ 993 | throw "invalid vs"; 994 | } 995 | if(!Utils.isArray(ind)){ 996 | throw "invalid ind"; 997 | } 998 | var x=0; 999 | var y=1; 1000 | var z=2; 1001 | var ns = []; 1002 | //对于每个vertex,初始化normal x, normal y, normal z 1003 | for(var i=0;i boundry; 47 | } 48 | else if(operator == this.GEQUAL){ 49 | b = num >= boundry; 50 | } 51 | else if(operator == this.LESS){ 52 | b = num < boundry; 53 | } 54 | else if(operator == this.LEQUAL){ 55 | b = num <= boundry; 56 | } 57 | return b; 58 | }, 59 | 60 | isPositive:function(v){ 61 | return this.judgeNumberBoundary(v,this.GREATER,0); 62 | }, 63 | 64 | isNegative:function(v){ 65 | return this.judgeNumberBoundary(v,this.LESS,0); 66 | }, 67 | 68 | isNonNegative:function(v){ 69 | return this.judgeNumberBoundary(v,this.GEQUAL,0); 70 | }, 71 | 72 | isNonPositive:function(v){ 73 | return this.judgeNumberBoundary(v,this.LEQUAL,0); 74 | }, 75 | 76 | isPositiveInteger:function(v){ 77 | return this.isPositive(v) && this.isInteger(v); 78 | }, 79 | 80 | isNonNegativeInteger:function(v){ 81 | return this.isNonNegative(v) && this.isInteger; 82 | }, 83 | 84 | isString:function(v){ 85 | return typeof v == "string"; 86 | }, 87 | 88 | isArray:function(v){ 89 | return Object.prototype.toString.call(v) === '[object Array]'; 90 | }, 91 | 92 | isFunction:function(v){ 93 | return typeof v == "function"; 94 | }, 95 | 96 | isNull:function(v){ 97 | return v === null; 98 | }, 99 | 100 | isUndefined:function(v){ 101 | return typeof v == "undefined"; 102 | }, 103 | 104 | isNullOrUndefined:function(v){ 105 | return this.isNull(v)||this.isUndefined(v); 106 | }, 107 | 108 | isJsonObject:function(v){ 109 | return typeof v == "object" && !this.isNull(v) && !this.isArray(v); 110 | }, 111 | 112 | isDom:function(v){ 113 | return v instanceof HTMLElement; 114 | }, 115 | 116 | forEach:function(arr,func){ 117 | if(!(this.isArray(arr))){ 118 | throw "invalid arr"; 119 | } 120 | if(!(this.isFunction(func))){ 121 | throw "invalid func"; 122 | } 123 | if(this.isFunction(Array.prototype.forEach)){ 124 | arr.forEach(func); 125 | } 126 | else{ 127 | for(var i=0;i 0){ 223 | var e = cloneArray[0]; 224 | var exist = this.some(simplifyArray,function(item){ 225 | return e.equals(item); 226 | }); 227 | if(!exist){ 228 | simplifyArray.push(e); 229 | } 230 | cloneArray.splice(0,1); 231 | } 232 | return simplifyArray; 233 | } 234 | }; 235 | 236 | export default utils; --------------------------------------------------------------------------------