diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ccdda08 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.check.workspaceVersion": false +} \ No newline at end of file diff --git a/README.md b/README.md index ae4c0f7..7d75b8d 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@

A WebGL virtual globe and map engine

- - + + ## WebGlobe WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引擎,支持诺基亚地图、微软Bing地图、腾讯地图、天地图、OpenStreetMap等。 @@ -17,20 +17,21 @@ Demo: https://ispring.github.io/WebGlobe/index.html **如果觉得不错,欢迎Star和Fork!** ## Setup dev environment - 1. 项目有两个主要的分支:develop分支和master分支,develop是主分支,开发的代码都提交到该分支;master分支用于release,当develop分支中的代码比较稳定切有重要更新的时候,会将develop分支的代码merge到master分支,然后通过master分支进行发布新版本。 - + 1. 项目有两个主要的分支:develop分支和master分支,develop是主分支,开发的代码都提交到该分支;master分支用于release,当develop分支中的代码比较稳定且有重要更新的时候,会将develop分支的代码merge到master分支,然后通过master分支进行发布新版本。 + 2. 项目采用TypeScript编写,编译成JavaScript运行,推荐使用[Visual Studio Code](http://code.visualstudio.com/)作为编辑器。 - - 3. 通过npm install -g typescript gulp-cli安装全局模块typescript和gulp。 - + + 3. 通过npm install -g typescript安装全局模块typescript。 + 4. 在项目的根目录下执行npm install,安装所需模块。 - - 5. 通过gulp进行编译打包,gulpfile中定义了多个task: - - clear用于清除编译打包的结果 - - compile用于将TypeScript版本的模块编译成JavaScript版本的AMD模块 - - bundle用于将TypeScript版本的模块打包成一个JavaScript压缩文件 - - build用于执行以上所有的task - + + 5. 使用gulp进行编译打包,gulpfile中定义了多个task,并在package.json中定义了对应的npm scripts: + - npm run clear 用于清除编译打包的结果 + - npm run compile 用于将TypeScript版本的模块编译成JavaScript版本的AMD模块 + - npm run bundle 用于将TypeScript版本的模块打包成一个JavaScript压缩文件 + - npm run build 用于执行以上所有的task + - npm start 用于执行build + 6. 通过index-src.html可以加载AMD格式的源码,方便调试;通过index-bundle.html可以加载打打包压缩后的JavaScript文件,减少了文件体积和网络请求数量,用于生产环境。 diff --git a/deprecated/WEB-INF/web.xml b/deprecated/WEB-INF/web.xml deleted file mode 100644 index 65d5512..0000000 --- a/deprecated/WEB-INF/web.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - index.html - index.jsp - - \ No newline at end of file diff --git a/deprecated/proxy.jsp b/deprecated/proxy.jsp deleted file mode 100644 index 4d5e102..0000000 --- a/deprecated/proxy.jsp +++ /dev/null @@ -1,42 +0,0 @@ -<%@page session="false"%> -<%@page import="java.net.*,java.io.*"%> -<% - try { - String reqUrl = request.getQueryString(); - URL url = new URL(reqUrl); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setDoOutput(true); - con.setRequestMethod(request.getMethod()); - if (request.getContentType() != null) { - con.setRequestProperty("Content-Type",request.getContentType()); - } - con.setRequestProperty("Referer", request.getHeader("Referer")); - int clength = request.getContentLength(); - if (clength > 0) { - con.setDoInput(true); - InputStream istream = request.getInputStream(); - OutputStream os = con.getOutputStream(); - final int length = 5000; - byte[] bytes = new byte[length]; - int bytesRead = 0; - while ((bytesRead = istream.read(bytes, 0, length)) > 0) { - os.write(bytes, 0, bytesRead); - } - } else { - con.setRequestMethod("GET"); - } - out.clear(); - out = pageContext.pushBody(); - OutputStream ostream = response.getOutputStream(); - response.setContentType(con.getContentType()); - InputStream in = con.getInputStream(); - final int length = 5000; - byte[] bytes = new byte[length]; - int bytesRead = 0; - while ((bytesRead = in.read(bytes, 0, length)) > 0) { - ostream.write(bytes, 0, bytesRead); - } - } catch (Exception e) { - response.setStatus(500); - } -%> diff --git a/index-bundle.html b/index-bundle.html index 7e5daab..c64b6b0 100644 --- a/index-bundle.html +++ b/index-bundle.html @@ -12,15 +12,14 @@ - + + - - + diff --git a/index-src.html b/index-src.html index 9b33a9a..ef67784 100644 --- a/index-src.html +++ b/index-src.html @@ -20,15 +20,14 @@ - - + - - + diff --git a/main.js b/main.js index 71ad393..145b7ff 100644 --- a/main.js +++ b/main.js @@ -1,47 +1,61 @@ window.onload = function() { - require(["world/Globe", "world/BingTiledLayer", "world/NokiaTiledLayer", "world/OsmTiledLayer", "world/SosoTiledLayer", "world/TiandituTiledLayer", "world/GoogleTiledLayer"], - function(Globe, BingTiledLayer, NokiaTiledLayer, OsmTiledLayer, SosoTiledLayer, TiandituTiledLayer, GoogleTiledLayer) { - - function startWebGL() { - var canvas = document.getElementById("canvasId"); - window.globe = new Globe(canvas); - var mapSelector = document.getElementById("mapSelector"); - mapSelector.onchange = changeTiledLayer; - changeTiledLayer(); - } - - function changeTiledLayer() { - var mapSelector = document.getElementById("mapSelector"); - mapSelector.blur(); - var newTiledLayer = null; - var args = null; - var value = mapSelector.value; - switch (value) { - case "bing": - newTiledLayer = new BingTiledLayer(); - break; - case "nokia": - newTiledLayer = new NokiaTiledLayer(); - break; - case "osm": - newTiledLayer = new OsmTiledLayer(); - break; - case "soso": - newTiledLayer = new SosoTiledLayer(); - break; - case "tianditu": - newTiledLayer = new TiandituTiledLayer(); - break; - default: - break; - } - - if (newTiledLayer) { - window.globe.setTiledLayer(newTiledLayer); - } - } - - - startWebGL(); - }); + require(["world/Kernel", "world/Globe", "world/layers/BingTiledLayer", "world/layers/NokiaTiledLayer", "world/layers/OsmTiledLayer", + "world/layers/SosoTiledLayer", "world/layers/TiandituTiledLayer", "world/layers/GoogleTiledLayer", "world/graphics/Atmosphere", + "world/layers/PoiLayer" + ], + function(Kernel, Globe, BingTiledLayer, NokiaTiledLayer, OsmTiledLayer, SosoTiledLayer, TiandituTiledLayer, GoogleTiledLayer, + Atmosphere, PoiLayer) { + + window.Kernel = Kernel; + + function startWebGL() { + var canvas = document.getElementById("canvasId"); + window.globe = new Globe(canvas); + + var mapSelector = document.getElementById("mapSelector"); + mapSelector.onchange = changeTiledLayer; + changeTiledLayer(); + + var atmosphere = Atmosphere.getInstance(); + + window.globe.scene.add(atmosphere); + + var poiLayer = new PoiLayer(); + window.globe.scene.add(poiLayer); + } + + function changeTiledLayer() { + var mapSelector = document.getElementById("mapSelector"); + mapSelector.blur(); + var newTiledLayer = null; + var args = null; + var value = mapSelector.value; + switch (value) { + case "bing": + newTiledLayer = new BingTiledLayer(); + break; + case "nokia": + newTiledLayer = new NokiaTiledLayer(); + break; + case "osm": + newTiledLayer = new OsmTiledLayer(); + break; + case "soso": + newTiledLayer = new SosoTiledLayer(); + break; + case "tianditu": + newTiledLayer = new TiandituTiledLayer(); + break; + default: + break; + } + + if (newTiledLayer) { + window.globe.setTiledLayer(newTiledLayer); + } + } + + + startWebGL(); + }); }; \ No newline at end of file diff --git a/package.json b/package.json index 4e5843e..41d876f 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,14 @@ { "name": "webglobe", - "version": "0.1.1", + "version": "0.3.3", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "clear": "gulp clear", + "compile": "gulp compile", + "bundle": "gulp bundle", + "build": "gulp build", + "start": "npm run build" }, "repository": { "type": "git", diff --git a/src/world/Camera.ts b/src/world/Camera.ts new file mode 100644 index 0000000..07d168b --- /dev/null +++ b/src/world/Camera.ts @@ -0,0 +1,1000 @@ +/// +import Kernel = require('./Kernel'); +import Utils = require('./Utils'); +import MathUtils = require('./math/Math'); +import Vertice = require('./math/Vertice'); +import Vector = require('./math/Vector'); +import Line = require('./math/Line'); +import Plan = require('./math/Plan'); +import TileGrid = require('./TileGrid'); +import Matrix = require('./math/Matrix'); +import Object3D = require('./Object3D'); + +export class CameraCore{ + constructor(private fov: number, private aspect: number, private near: number, private far: number, private realLevel: number, private matrix: Matrix){ + + } + + getFov(){ + return this.fov; + } + + getAspect(){ + return this.aspect; + } + + getNear(){ + return this.near; + } + + getFar(){ + return this.far; + } + + getRealLeavel(){ + return this.realLevel; + } + + getMatrix(){ + return this.matrix; + } + + equals(other: CameraCore): boolean{ + if(!other){ + return false; + } + return this.fov === other.getFov() && + this.aspect === other.getAspect() && + this.near === other.getNear() && + this.far === other.getFar() && + this.realLevel === other.getRealLeavel() && + this.matrix.equals(other.getMatrix()); + } +} + +class Camera extends Object3D { + private readonly initFov: number; + private readonly animationDuration: number = 200;//层级变化的动画周期,毫秒 + private readonly nearFactor: number = 0.6; + private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; + private readonly maxPitch = 40; + + //旋转的时候,绕着视线与地球交点进行旋转 + //定义抬头时,旋转角为正值 + private isZeroPitch: boolean = true;//表示当前Camera视线有没有发生倾斜 + + private level: number = -1; //当前渲染等级 + private realLevel: number = -2;//可能是正数,可能是非整数,非整数表示缩放动画过程中的level + + private lastRealLevel: number = -3;//上次render()时所用到的this.realLevel + private lastMatrix: Matrix;//上次render()时的this.matrix + private lastFov: number = -1; + private lastAspect: number = -1; + private lastNear: number = -1; + private lastFar: number = -1; + + private viewMatrix: Matrix;//视点矩阵,即Camera模型矩阵的逆矩阵 + private projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far + private projViewMatrix: Matrix;//获取投影矩阵与视点矩阵的乘积 + + private matrixForDraw: Matrix; + private viewMatrixForDraw: Matrix; + private projMatrixForDraw: Matrix; + private projViewMatrixForDraw: Matrix;//实际传递给shader的矩阵是projViewMatrixForDraw,而不是projViewMatrix + + private animating: boolean = false; + + //this.near一旦初始化之后就不应该再修改 + //this.far可以动态计算 + //this.aspect在Viewport改变后重新计算 + //this.fov可以调整以实现缩放效果 + constructor(private fov = 45, private aspect = 1, private near = 1, private far = 100) { + super(); + this.initFov = this.fov; + this.lastMatrix = new Matrix(); + this.lastMatrix.setUniqueValue(0); + this.projMatrix = new Matrix(); + this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); + this._initCameraPosition(); + } + + private _setPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { + this._rawSetPerspectiveMatrix(fov, aspect, near, far); + this._updateFar(); + } + + private _rawSetPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100, projMatrix: Matrix = this.projMatrix): void { + //https://github.com/toji/gl-matrix/blob/master/src/gl-matrix/mat4.js#L1788 + if (this.projMatrix === projMatrix) { + this.fov = fov; + this.aspect = aspect; + this.near = near; + this.far = far; + } + + var mat = [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]; + var halfFov = fov * Math.PI / 180 / 2; + var f = 1 / Math.tan(halfFov); + var nf = 1 / (near - far); + + mat[0] = f / aspect; + mat[5] = f; + mat[10] = (far + near) * nf; + mat[11] = -1; + mat[14] = 2 * near * far * nf; + mat[15] = 0; + + //by comparision with matrixProjection.exe and glMatrix, the 11th element is always -1 + projMatrix.setElements( + mat[0], mat[1], mat[2], mat[3], + mat[4], mat[5], mat[6], mat[7], + mat[8], mat[9], mat[10], mat[11], + mat[12], mat[13], mat[14], mat[15] + ); + } + + private _setFov(fov: number): void { + if (!(fov > 0)) { + throw "invalid fov:" + fov; + } + this._setPerspectiveMatrix(fov, this.aspect, this.near, this.far); + } + + setAspect(aspect: number): void { + if (!(aspect > 0)) { + throw "invalid aspect:" + aspect; + } + this._setPerspectiveMatrix(this.fov, aspect, this.near, this.far); + } + + private _updateFar(): void { + // var far = this._getMinimalFar(this.matrix.getPosition()); + // this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); + } + + private _getMinimalFar(cameraPosition: Vertice): number { + //重新计算far,保持far在满足正常需求情况下的最小值 + //far值:视点与地球切面的距离 + var distance2EarthOrigin = Vector.fromVertice(cameraPosition).getLength(); + var far = Math.sqrt(distance2EarthOrigin * distance2EarthOrigin - Kernel.EARTH_RADIUS * Kernel.EARTH_RADIUS); + far *= 1.05; + return far; + } + + //更新各种矩阵,理论上只在用户交互的时候调用就可以 + update(force: boolean = false): void { + if(force || this._isNeedUpdate()){ + this._normalUpdate(); + this._updateProjViewMatrixForDraw(); + } + this.lastFov = this.fov; + this.lastAspect = this.aspect; + this.lastNear = this.near; + this.lastFar = this.far; + this.lastRealLevel = this.realLevel; + this.lastMatrix.setMatrixByOther(this.matrix); + } + + getCameraCore(){ + return new CameraCore(this.fov, this.aspect, this.near, this.far, this.realLevel, this.matrix.clone()); + } + + private _isNeedUpdate(): boolean{ + return (this.fov !== this.lastFov) || + (this.aspect !== this.lastAspect) || + (this.near !== this.lastNear) || + (this.far !== this.lastFar) || + (this.realLevel !== this.lastRealLevel) || + (!this.matrix.equals(this.lastMatrix)); + } + + getProjViewMatrixForDraw(): Matrix { + return this.projViewMatrixForDraw; + } + + _normalUpdate() { + //视点矩阵是camera的模型矩阵的逆矩阵 + this.viewMatrix = this.matrix.getInverseMatrix(); + + //通过修改far值更新projMatrix + this._updateFar(); + + //更新projViewMatrix + this.projViewMatrix = this.projMatrix.multiplyMatrix(this.viewMatrix); + } + + _updateProjViewMatrixForDraw() { + this.matrixForDraw = this.matrix.clone(); + + //通过修改position以更新matrix + var newFov = this._updatePositionAndFov(this.matrixForDraw); + var aspect = this.aspect; + var near = this.near; + + //计算newFar + var newPosition = this.matrixForDraw.getPosition(); + var newFar = this.far; //this._getMinimalFar(newPosition); + + //根据newFov和newFar重新计算 + this.projMatrixForDraw = new Matrix(); + this._rawSetPerspectiveMatrix(newFov, aspect, near, newFar, this.projMatrixForDraw); + + //在_updatePositionAndFov()方法调用之后再计算newViewMatrix + this.viewMatrixForDraw = this.matrixForDraw.getInverseMatrix(); + + //最后计算projViewMatrixForDraw + this.projViewMatrixForDraw = this.projMatrixForDraw.multiplyMatrix(this.viewMatrixForDraw); + } + + //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov + private _updatePositionAndFov(cameraMatrix: Matrix): number { + //是否满足near值,和fov没有关系,和position有关,但是改变position的话,fov也要相应变动以满足对应的缩放效果 + const currentLevel = this.animating ? this.realLevel : this.level; + + //safeLevel不是整数 + var safeLevel = this._getSafeThresholdLevelForNear(); + + if (currentLevel > safeLevel) { + //摄像机距离地球太近,导致不满足视景体的near值, + //我们需要将摄像机的位置拉远,以满足near值 + this._updatePositionByLevel(safeLevel, cameraMatrix); + //比如safeLevel是10,而currentLevel是11,则deltaLevel为1 + var deltaLevel = currentLevel - safeLevel; + //摄像机位置与地球表面距离变大之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 + //deltaLevel应该为正正数,计算出的newFov应该比this.initFov要小 + var newFov = this._calculateFovByDeltaLevel(this.initFov, deltaLevel); + return newFov; + } else { + this._updatePositionByLevel(currentLevel, cameraMatrix); + return this.initFov; + } + } + + //计算从第几级level开始不满足视景体的near值 + //比如第10级满足near,第11级不满足near,那么返回10 + private _getSafeThresholdLevelForNear() { + var thresholdNear = this.near * this.nearFactor; + var pow2level = this.baseTheoryDistanceFromCamera2EarthSurface / thresholdNear; + var level = (Math).log2(pow2level); + //return Math.floor(level); + return level; + } + + /** + * 根据层级计算出摄像机应该放置到距离地球表面多远的位置 + * @param level + * @return {*} + */ + private _getTheoryDistanceFromCamera2EarthSurface(level: number): number { + return this.baseTheoryDistanceFromCamera2EarthSurface / Math.pow(2, level); + } + + //fov从oldFov变成了newFov,计算相当于缩放了几级level + //比如从10级缩放到了第11级,fov从30变成了15,即oldFov为30,newFov为15,deltaLevel为1 + //通过Math.log2()计算出结果,所以返回的是小数,可能是正数也可能是负数 + private _calculateDeltaLevelByFov(oldFov: number, newFov: number): number { + //tan(halfFov) = h / distance,level不同的情况下h不变 + //h1 = l1*tanθ1 + //h2 = l2*tanθ2 + //l2 = l1 * Math.pow(2, deltaLevel) + //deltaLevel = Math.log2(tanθ1 / tanθ2) + var radianOldFov = MathUtils.degreeToRadian(oldFov); + var halfRadianOldFov = radianOldFov / 2; + var tanOld = Math.tan(halfRadianOldFov); + + var radianNewFov = MathUtils.degreeToRadian(newFov); + var halfRadianNewFov = radianNewFov / 2; + var tanNew = Math.tan(halfRadianNewFov); + + var deltaLevel = (Math).log2(tanOld / tanNew); + return deltaLevel; + } + + //通过调整fov的值造成层级缩放的效果,比如在第10级的时候,oldFov为正常的30度,当放大到11级的时候,deltaLevel为1,计算出的新的newFov为15度多 + private _calculateFovByDeltaLevel(oldFov: number, deltaLevel: number): number { + //tan(halfFov) = h / distance,level不同的情况下h不变 + //h1 = l1*tanθ1 + //h2 = l2*tanθ2 + //l2 = l1 * Math.pow(2, deltaLevel) + var radianOldFov = MathUtils.degreeToRadian(oldFov); + var halfRadianOldFov = radianOldFov / 2; + var tanOld = Math.tan(halfRadianOldFov); + var tanNew = tanOld / Math.pow(2, deltaLevel); + var halfRadianNewFov = Math.atan(tanNew); + var radianNewFov = halfRadianNewFov * 2; + var newFov = MathUtils.radianToDegree(radianNewFov); + return newFov; + } + + getLevel(): number { + return this.level; + } + + setLevel(level: number): void { + if (!(Utils.isNonNegativeInteger(level))) { + throw "invalid level:" + level; + } + level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 + if (level === this.level) { + return; + } + var isLevelChanged = this._updatePositionByLevel(level, this.matrix); + //不要在this._updatePositionByLevel()方法中更新this.level,因为这会影响animateToLevel()方法 + this.level = level; + this.realLevel = level; + Kernel.globe.refresh(); + } + + private _initCameraPosition() { + var initLevel = 0; + var length = this._getTheoryDistanceFromCamera2EarthSurface(initLevel) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 + var origin = new Vertice(0, 0, 0); + var vector = this.getLightDirection().getOpposite(); + vector.setLength(length); + var newPosition = vector.getVertice(); + this._look(newPosition, origin); + } + + //设置观察到的层级,不要在该方法中修改this.level的值 + private _updatePositionByLevel(level: number, cameraMatrix: Matrix) { + var globe = Kernel.globe; + var intersects = this._getDirectionIntersectPointWithEarth(cameraMatrix); + if (intersects.length === 0) { + throw "no intersect"; + } + var intersect = intersects[0]; + var theoryDistance2Interscet = this._getTheoryDistanceFromCamera2EarthSurface(level); + var vector = cameraMatrix.getVectorZ(); + vector.setLength(theoryDistance2Interscet); + var newCameraPosition = Vector.verticePlusVector(intersect, vector); + cameraMatrix.setPosition(newCameraPosition); + } + + setDeltaPitch(deltaPitch: number) { + var currentPitch = this.getPitch(); + var newPitch = currentPitch + deltaPitch; + if (newPitch > this.maxPitch) { + return; + } + if (newPitch < 0) { + newPitch = 0; + } + + //计算最终的deltaPitch + deltaPitch = newPitch - currentPitch; + if (deltaPitch === 0) { + return; + } + + var intersects = this._getDirectionIntersectPointWithEarth(this.matrix); + if (intersects.length === 0) { + throw "no intersects"; + } + var intersect = intersects[0]; + + var deltaRadian = MathUtils.degreeToRadian(deltaPitch); + //先不对this.matrix进行更新,对其拷贝进行更新 + var matrix = this.matrix.clone(); + //将matrix移动到交点位置 + matrix.setPosition(intersect); + //旋转 + matrix.localRotateX(deltaRadian); + //更新matrix的position + this._updatePositionByLevel(this.level, matrix); + + //刷新 + this.isZeroPitch = newPitch === 0; + this.matrix = matrix; + Kernel.globe.refresh(); + } + + //pitch表示Camera视线的倾斜角度,初始值为0,表示视线经过球心,单位为角度,范围是[0, this.maxPitch] + getPitch(): number { + if (this.isZeroPitch) { + return 0; + } + var intersects = this._getDirectionIntersectPointWithEarth(this.matrix); + if (intersects.length === 0) { + throw "no intersects"; + } + var intersect = intersects[0]; + + //计算夹角 + var vectorOrigin2Intersect = Vector.fromVertice(intersect); + var length1 = vectorOrigin2Intersect.getLength(); + var vectorIntersect2Camera = Vector.verticeMinusVertice(this.getPosition(), intersect); + var length2 = vectorIntersect2Camera.getLength(); + var cosθ = vectorOrigin2Intersect.dot(vectorIntersect2Camera) / (length1 * length2); + var radian = MathUtils.acosSafely(cosθ); + + //计算夹角的正负 + var crossVector = vectorOrigin2Intersect.cross(vectorIntersect2Camera); + var xAxisDirection = this.matrix.getVectorX() + if (crossVector.dot(xAxisDirection)) { + //正值 + radian = Math.abs(radian); + } else { + //负值 + radian = - Math.abs(radian); + } + + var pitch = MathUtils.radianToDegree(radian); + + if(pitch >= 90){ + throw `Invalid pitch: ${pitch}`; + } + + return pitch; + } + + //计算拾取射线与地球的交点,以笛卡尔空间直角坐标系坐标数组的形式返回 + //该方法需要projViewMatrixForDraw系列矩阵进行计算 + getPickCartesianCoordInEarthByCanvas(canvasX: number, canvasY: number): Vertice[] { + this.update(); + + //暂存projViewMatrix系列矩阵 + var matrix = this.matrix; + var viewMatrix = this.viewMatrix; + var projMatrix = this.projMatrix; + var projViewMatrix = this.projViewMatrix; + + //将projViewMatrix系列矩阵赋值为projViewMatrixForDraw系列矩阵 + this.matrix = this.matrixForDraw; + this.viewMatrix = this.viewMatrixForDraw; + this.projMatrix = this.projMatrixForDraw; + this.projViewMatrix = this.projViewMatrixForDraw; + + //基于projViewMatrixForDraw系列矩阵进行计算,应该没有误差 + var pickDirection = this._getPickDirectionByCanvas(canvasX, canvasY); + var p = this.getPosition(); + var line = new Line(p, pickDirection); + var result = this._getPickCartesianCoordInEarthByLine(line); + + //还原projViewMatrix系列矩阵 + this.matrix = matrix; + this.viewMatrix = viewMatrix; + this.projMatrix = projMatrix; + this.projViewMatrix = projViewMatrix; + + return result; + } + + getLightDirection(): Vector { + var dirVertice = this.matrix.getVectorZ(); + var direction = new Vector(-dirVertice.x, -dirVertice.y, -dirVertice.z); + direction.normalize(); + return direction; + } + + getDistance2EarthSurface(): number { + var position = this.getPosition(); + var length2EarthSurface = Vector.fromVertice(position).getLength() - Kernel.EARTH_RADIUS; + return length2EarthSurface; + } + + getDistance2EarthOrigin(): number{ + var position = this.getPosition(); + return Vector.fromVertice(position).getLength(); + } + + isAnimating(): boolean { + return this.animating; + } + + animateToLevel(newLevel: number): void { + if (this.isAnimating()) { + return; + } + + if (!(Utils.isNonNegativeInteger(newLevel))) { + throw "invalid level:" + newLevel; + } + var newCameraMatrix = this.matrix.clone(); + this._updatePositionByLevel(newLevel, newCameraMatrix); + var newPosition = newCameraMatrix.getPosition(); + + var oldPosition = this.getPosition(); + var span = this.animationDuration; + var singleSpan = 1000 / 60; + var count = Math.floor(span / singleSpan); + var deltaX = (newPosition.x - oldPosition.x) / count; + var deltaY = (newPosition.y - oldPosition.y) / count; + var deltaZ = (newPosition.z - oldPosition.z) / count; + var deltaLevel = (newLevel - this.level) / count; + var start: number = -1; + this.realLevel = this.level; + this.animating = true; + + var callback = (timestap: number) => { + if (start < 0) { + start = timestap; + } + var a = timestap - start; + if (a >= span) { + this.animating = false; + this.realLevel = newLevel; + this.setLevel(newLevel); + } else { + this.realLevel += deltaLevel; + var p = this.getPosition(); + this.setPosition(new Vertice(p.x + deltaX, p.y + deltaY, p.z + deltaZ)); + requestAnimationFrame(callback); + } + }; + requestAnimationFrame(callback); + } + + private _look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { + var cameraPntCopy = cameraPnt.clone(); + var targetPntCopy = targetPnt.clone(); + var up = upDirection.clone(); + + var zAxis = new Vector( + cameraPntCopy.x - targetPntCopy.x, + cameraPntCopy.y - targetPntCopy.y, + cameraPntCopy.z - targetPntCopy.z + ); + zAxis.normalize(); + var xAxis = up.cross(zAxis).normalize(); + var yAxis = zAxis.cross(xAxis).normalize(); + + this.matrix.setVectorX(xAxis); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置X轴方向 + this.matrix.setVectorY(yAxis); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置Y轴方向 + this.matrix.setVectorZ(zAxis); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置Z轴方向 + this.matrix.setPosition(cameraPntCopy); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置偏移量 + this.matrix.setLastRowDefault(); + + this._updateFar(); + } + + private _lookAt(targetPnt: Vertice, upDirection?: Vector): void { + var targetPntCopy = targetPnt.clone(); + var position = this.getPosition(); + this._look(position, targetPntCopy, upDirection); + } + + //根据canvasX和canvasY获取拾取向量 + private _getPickDirectionByCanvas(canvasX: number, canvasY: number): Vector { + var ndcXY = MathUtils.convertPointFromCanvasToNDC(canvasX, canvasY); + var pickDirection = this._getPickDirectionByNDC(ndcXY[0], ndcXY[1]); + return pickDirection; + } + + //获取cameraMatrix视线与地球的交点 + private _getDirectionIntersectPointWithEarth(cameraMatrix: Matrix): Vertice[] { + var dir = cameraMatrix.getVectorZ().getOpposite(); + var p = cameraMatrix.getPosition(); + var line = new Line(p, dir); + var result = this._getPickCartesianCoordInEarthByLine(line); + return result; + } + + //根据ndcX和ndcY获取拾取向量 + private _getPickDirectionByNDC(ndcX: number, ndcY: number): Vector { + var verticeInNDC = new Vertice(ndcX, ndcY, 0.499); + var verticeInWorld = this._convertVerticeFromNdcToWorld(verticeInNDC); + var cameraPositon = this.getPosition(); //摄像机的世界坐标 + var pickDirection = Vector.verticeMinusVertice(verticeInWorld, cameraPositon); + pickDirection.normalize(); + return pickDirection; + } + + //获取直线与地球的交点,该方法与MathUtils.getLineIntersectPointWithEarth功能基本一样,只不过该方法对相交点进行了远近排序 + private _getPickCartesianCoordInEarthByLine(line: Line): Vertice[] { + var result: Vertice[] = []; + //pickVertice是笛卡尔空间直角坐标系中的坐标 + var pickVertices = MathUtils.getLineIntersectPointWithEarth(line); + if (pickVertices.length === 0) { + //没有交点 + result = []; + } else if (pickVertices.length == 1) { + //一个交点 + result = pickVertices; + } else if (pickVertices.length == 2) { + //两个交点 + var pickVerticeA = pickVertices[0]; + var pickVerticeB = pickVertices[1]; + var cameraVertice = this.getPosition(); + var lengthA = MathUtils.getLengthFromVerticeToVertice(cameraVertice, pickVerticeA); + var lengthB = MathUtils.getLengthFromVerticeToVertice(cameraVertice, pickVerticeB); + //将距离人眼更近的那个点放到前面 + result = lengthA <= lengthB ? [pickVerticeA, pickVerticeB] : [pickVerticeB, pickVerticeA]; + } + return result; + } + + private _getPickCartesianCoordInEarthByNDC(ndcX: number, ndcY: number): Vertice[] { + var pickDirection = this._getPickDirectionByNDC(ndcX, ndcY); + var p = this.getPosition(); + var line = new Line(p, pickDirection); + var result = this._getPickCartesianCoordInEarthByLine(line); + return result; + } + + //得到摄像机的XOZ平面的方程 + private _getPlanXOZ(): Plan { + var position = this.getPosition(); + var direction = this.getLightDirection(); + var plan = MathUtils.getCrossPlaneByLine(position, direction); + return plan; + } + + //点变换: World->NDC + private _convertVerticeFromWorldToNDC(verticeInWorld: Vertice): Vertice { + var columnWorld = [verticeInWorld.x, verticeInWorld.y, verticeInWorld.z, 1]; + var columnProject = this.projViewMatrix.multiplyColumn(columnWorld); + var w = columnProject[3]; + var columnNDC: number[] = []; + columnNDC[0] = columnProject[0] / w; + columnNDC[1] = columnProject[1] / w; + columnNDC[2] = columnProject[2] / w; + columnNDC[3] = 1; + var verticeInNDC = new Vertice(columnNDC[0], columnNDC[1], columnNDC[2]); + return verticeInNDC; + } + + //点变换: NDC->World + private _convertVerticeFromNdcToWorld(verticeInNDC: Vertice): Vertice { + var columnNDC: number[] = [verticeInNDC.x, verticeInNDC.y, verticeInNDC.z, 1]; //NDC归一化坐标 + var inverseProj = this.projMatrix.getInverseMatrix(); //投影矩阵的逆矩阵 + var columnCameraTemp = inverseProj.multiplyColumn(columnNDC); //带引号的“视坐标” + var cameraX = columnCameraTemp[0] / columnCameraTemp[3]; + var cameraY = columnCameraTemp[1] / columnCameraTemp[3]; + var cameraZ = columnCameraTemp[2] / columnCameraTemp[3]; + var cameraW = 1; + var columnCamera = [cameraX, cameraY, cameraZ, cameraW]; //真实的视坐标 + var columnWorld = this.matrix.multiplyColumn(columnCamera); //单击点的世界坐标 + var verticeInWorld = new Vertice(columnWorld[0], columnWorld[1], columnWorld[2]); + return verticeInWorld; + } + + //点变换: Camera->World + private _convertVerticeFromCameraToWorld(verticeInCamera: Vertice): Vertice { + var verticeInCameraCopy = verticeInCamera.clone(); + var column = [verticeInCameraCopy.x, verticeInCameraCopy.y, verticeInCameraCopy.z, 1]; + var column2 = this.matrix.multiplyColumn(column); + var verticeInWorld = new Vertice(column2[0], column2[1], column2[2]); + return verticeInWorld; + } + + //向量变换: Camera->World + private _convertVectorFromCameraToWorld(vectorInCamera: Vector): Vector { + var vectorInCameraCopy = vectorInCamera.clone(); + var verticeInCamera = vectorInCameraCopy.getVertice(); + var verticeInWorld = this._convertVerticeFromCameraToWorld(verticeInCamera); + var originInWorld = this.getPosition(); + var vectorInWorld = Vector.verticeMinusVertice(verticeInWorld, originInWorld); + vectorInWorld.normalize(); + return vectorInWorld; + } + + //判断世界坐标系中的点是否在Canvas中可见 + //options: verticeInNDC,threshold + private _isWorldVerticeVisibleInCanvas(verticeInWorld: Vertice, options: any = {}): boolean { + var threshold = typeof options.threshold == "number" ? Math.abs(options.threshold) : 1; + var cameraP = this.getPosition(); + var dir = Vector.verticeMinusVertice(verticeInWorld, cameraP); + var line = new Line(cameraP, dir); + var pickResult = this._getPickCartesianCoordInEarthByLine(line); + if (pickResult.length > 0) { + var pickVertice = pickResult[0]; + var length2Vertice = MathUtils.getLengthFromVerticeToVertice(cameraP, verticeInWorld); + var length2Pick = MathUtils.getLengthFromVerticeToVertice(cameraP, pickVertice); + if (length2Vertice < length2Pick + 5) { + if (!(options.verticeInNDC instanceof Vertice)) { + options.verticeInNDC = this._convertVerticeFromWorldToNDC(verticeInWorld); + } + var result = options.verticeInNDC.x >= -1 && options.verticeInNDC.x <= 1 && options.verticeInNDC.y >= -threshold && options.verticeInNDC.y <= 1; + return result; + } + } + return false; + } + + //判断地球表面的某个经纬度在Canvas中是否应该可见 + //options: verticeInNDC + private _isGeoVisibleInCanvas(lon: number, lat: number, options?: any): boolean { + var verticeInWorld = MathUtils.geographicToCartesianCoord(lon, lat); + var result = this._isWorldVerticeVisibleInCanvas(verticeInWorld, options); + return result; + } + + /** + * 算法,一个切片需要渲染需要满足如下三个条件: + * 1.至少要有一个点在Canvas中可见 + * 2.NDC面积足够大 + * 3.形成的NDC四边形是顺时针方向 + */ + //获取level层级下的可见切片 + //options: + getVisibleTilesByLevel(level: number, options: any = {}): TileGrid[] { + if (!(level >= 0)) { + throw "invalid level"; + } + var result: TileGrid[] = []; + //向左、向右、向上、向下最大的循环次数 + var LOOP_LIMIT = Math.min(10, Math.pow(2, level) - 1); + + var mathOptions = { + maxSize: Math.pow(2, level) + }; + + function checkVisible(visibleInfo: any) { + if (visibleInfo.area >= 5000 && visibleInfo.clockwise) { + if (visibleInfo.visibleCount >= 1) { + return true; + } + } + return false; + } + + //处理一整行 + function handleRow(centerRow: number, centerColumn: number): TileGrid[] { + var result: TileGrid[] = []; + var grid = new TileGrid(level, centerRow, centerColumn); // {level:level,row:centerRow,column:centerColumn}; + var visibleInfo = this._getTileVisibleInfo(grid.level, grid.row, grid.column, options); + var isRowCenterVisible = checkVisible(visibleInfo); + if (isRowCenterVisible) { + (grid as any).visibleInfo = visibleInfo; + result.push(grid); + + //向左遍历至不可见 + var leftLoopTime = 0; //向左循环的次数 + var leftColumn = centerColumn; + var visible: boolean; + while (leftLoopTime < LOOP_LIMIT) { + leftLoopTime++; + grid = TileGrid.getTileGridByBrother(level, centerRow, leftColumn, MathUtils.LEFT, mathOptions); + leftColumn = grid.column; + visibleInfo = this._getTileVisibleInfo(grid.level, grid.row, grid.column, options); + visible = checkVisible(visibleInfo); + if (visible) { + (grid).visibleInfo = visibleInfo; + result.push(grid); + } else { + break; + } + } + + //向右遍历至不可见 + var rightLoopTime = 0; //向右循环的次数 + var rightColumn = centerColumn; + while (rightLoopTime < LOOP_LIMIT) { + rightLoopTime++; + grid = TileGrid.getTileGridByBrother(level, centerRow, rightColumn, MathUtils.RIGHT, mathOptions); + rightColumn = grid.column; + visibleInfo = this._getTileVisibleInfo(grid.level, grid.row, grid.column, options); + visible = checkVisible(visibleInfo); + if (visible) { + (grid).visibleInfo = visibleInfo; + result.push(grid); + } else { + break; + } + } + } + return result; + } + + var verticalCenterInfo = this._getVerticalVisibleCenterInfo(); + var centerGrid = TileGrid.getTileGridByGeo(verticalCenterInfo.lon, verticalCenterInfo.lat, level); + var handleRowThis = handleRow.bind(this); + + var rowResult = handleRowThis(centerGrid.row, centerGrid.column); + result = result.concat(rowResult); + var grid: TileGrid; + + //循环向下处理至不可见 + var bottomLoopTime = 0; //向下循环的次数 + var bottomRow = centerGrid.row; + while (bottomLoopTime < LOOP_LIMIT) { + bottomLoopTime++; + grid = TileGrid.getTileGridByBrother(level, bottomRow, centerGrid.column, MathUtils.BOTTOM, mathOptions); + bottomRow = grid.row; + rowResult = handleRowThis(grid.row, grid.column); + if (rowResult.length > 0) { + result = result.concat(rowResult); + } else { + //已经向下循环到不可见,停止向下循环 + break; + } + } + + //循环向上处理至不可见 + var topLoopTime = 0; //向上循环的次数 + var topRow = centerGrid.row; + while (topLoopTime < LOOP_LIMIT) { + topLoopTime++; + grid = TileGrid.getTileGridByBrother(level, topRow, centerGrid.column, MathUtils.TOP, mathOptions); + topRow = grid.row; + rowResult = handleRowThis(grid.row, grid.column); + if (rowResult.length > 0) { + result = result.concat(rowResult); + } else { + //已经向上循环到不可见,停止向上循环 + break; + } + } + + return result; + } + + //options: threshold + private _getTileVisibleInfo(level: number, row: number, column: number, options: any = {}): any { + if (!(level >= 0)) { + throw "invalid level"; + } + if (!(row >= 0)) { + throw "invalid row"; + } + if (!(column >= 0)) { + throw "invalid column"; + } + + var threshold = typeof options.threshold == "number" ? Math.abs(options.threshold) : 1; + var result: any = { + lb: { + lon: null, + lat: null, + verticeInWorld: null, + verticeInNDC: null, + visible: false + }, + lt: { + lon: null, + lat: null, + verticeInWorld: null, + verticeInNDC: null, + visible: false + }, + rt: { + lon: null, + lat: null, + verticeInWorld: null, + verticeInNDC: null, + visible: false + }, + rb: { + lon: null, + lat: null, + verticeInWorld: null, + verticeInNDC: null, + visible: false + }, + Egeo: null, + visibleCount: 0, + clockwise: false, + width: null, + height: null, + area: null + }; + + result.Egeo = MathUtils.getTileGeographicEnvelopByGrid(level, row, column); + var tileMinLon = result.Egeo.minLon; + var tileMaxLon = result.Egeo.maxLon; + var tileMinLat = result.Egeo.minLat; + var tileMaxLat = result.Egeo.maxLat; + + //左下角 + result.lb.lon = tileMinLon; + result.lb.lat = tileMinLat; + result.lb.verticeInWorld = MathUtils.geographicToCartesianCoord(result.lb.lon, result.lb.lat); + result.lb.verticeInNDC = this._convertVerticeFromWorldToNDC(result.lb.verticeInWorld); + result.lb.visible = this._isWorldVerticeVisibleInCanvas(result.lb.verticeInWorld, { + verticeInNDC: result.lb.verticeInNDC, + threshold: threshold + }); + if (result.lb.visible) { + result.visibleCount++; + } + + //左上角 + result.lt.lon = tileMinLon; + result.lt.lat = tileMaxLat; + result.lt.verticeInWorld = MathUtils.geographicToCartesianCoord(result.lt.lon, result.lt.lat); + result.lt.verticeInNDC = this._convertVerticeFromWorldToNDC(result.lt.verticeInWorld); + result.lt.visible = this._isWorldVerticeVisibleInCanvas(result.lt.verticeInWorld, { + verticeInNDC: result.lt.verticeInNDC, + threshold: threshold + }); + if (result.lt.visible) { + result.visibleCount++; + } + + //右上角 + result.rt.lon = tileMaxLon; + result.rt.lat = tileMaxLat; + result.rt.verticeInWorld = MathUtils.geographicToCartesianCoord(result.rt.lon, result.rt.lat); + result.rt.verticeInNDC = this._convertVerticeFromWorldToNDC(result.rt.verticeInWorld); + result.rt.visible = this._isWorldVerticeVisibleInCanvas(result.rt.verticeInWorld, { + verticeInNDC: result.rt.verticeInNDC, + threshold: threshold + }); + if (result.rt.visible) { + result.visibleCount++; + } + + //右下角 + result.rb.lon = tileMaxLon; + result.rb.lat = tileMinLat; + result.rb.verticeInWorld = MathUtils.geographicToCartesianCoord(result.rb.lon, result.rb.lat); + result.rb.verticeInNDC = this._convertVerticeFromWorldToNDC(result.rb.verticeInWorld); + result.rb.visible = this._isWorldVerticeVisibleInCanvas(result.rb.verticeInWorld, { + verticeInNDC: result.rb.verticeInNDC, + threshold: threshold + }); + if (result.rb.visible) { + result.visibleCount++; + } + + var ndcs: Vertice[] = [result.lb.verticeInNDC, result.lt.verticeInNDC, result.rt.verticeInNDC, result.rb.verticeInNDC]; + //计算方向 + var vector03 = Vector.verticeMinusVertice(ndcs[3], ndcs[0]); + vector03.z = 0; + var vector01 = Vector.verticeMinusVertice(ndcs[1], ndcs[0]); + vector01.z = 0; + var cross = vector03.cross(vector01); + result.clockwise = cross.z > 0; + //计算面积 + 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; + 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; + result.width = Math.floor((topWidth + bottomWidth) / 2); + 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; + 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; + result.height = Math.floor((leftHeight + rightHeight) / 2); + result.area = result.width * result.height; + + return result; + } + + //地球一直是关于纵轴中心对称的,获取垂直方向上中心点信息 + private _getVerticalVisibleCenterInfo(): any { + var result = { + ndcY: null, + pIntersect: null, + lon: null, + lat: null + }; + var pickResults: Vertice[]; + if (this.isZeroPitch) { + result.ndcY = 0; + } else { + var count = 10; + var delta = 2.0 / count; + var topNdcY = 1; + var bottomNdcY = -1; + var ndcY: number; + //从上往下找topNdcY + for (ndcY = 1.0; ndcY >= -1.0; ndcY -= delta) { + pickResults = this._getPickCartesianCoordInEarthByNDC(0, ndcY); + if (pickResults.length > 0) { + topNdcY = ndcY; + break; + } + } + + //从下往上找 + for (ndcY = -1.0; ndcY <= 1.0; ndcY += delta) { + pickResults = this._getPickCartesianCoordInEarthByNDC(0, ndcY); + if (pickResults.length > 0) { + bottomNdcY = ndcY; + break; + } + } + result.ndcY = (topNdcY + bottomNdcY) / 2; + } + pickResults = this._getPickCartesianCoordInEarthByNDC(0, result.ndcY); + result.pIntersect = pickResults[0]; + var lonlat = MathUtils.cartesianCoordToGeographic(result.pIntersect); + result.lon = lonlat[0]; + result.lat = lonlat[1]; + return result; + } +} + +export default Camera; \ No newline at end of file diff --git a/src/world/Definitions.d.ts b/src/world/Definitions.d.ts index 6a3859f..fcc57a3 100644 --- a/src/world/Definitions.d.ts +++ b/src/world/Definitions.d.ts @@ -1,3 +1,5 @@ +import Matrix = require('./math/Matrix'); + interface WebGLProgramExtension extends WebGLProgram{ uMVMatrix: WebGLUniformLocation; uPMatrix: WebGLUniformLocation; @@ -9,4 +11,14 @@ interface WebGLProgramExtension extends WebGLProgram{ export interface WebGLRenderingContextExtension extends WebGLRenderingContext{ shaderProgram: WebGLProgramExtension; +} + +export interface IMockCamera{ + fov: number; + aspect: number; + near: number; + far: number; + realLevel: number; + matrix: Matrix; + equals(other: IMockCamera): boolean; } \ No newline at end of file diff --git a/src/world/Elevation.ts b/src/world/Elevation.ts deleted file mode 100644 index eae4745..0000000 --- a/src/world/Elevation.ts +++ /dev/null @@ -1,308 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Utils = require('./Utils'); -import MathUtils = require('./Math'); -import TileGrid = require('./TileGrid'); - -const Elevation = { - //sampleserver4.arcgisonline.com - //23.21.85.73 - elevationUrl: "//sampleserver4.arcgisonline.com/ArcGIS/rest/services/Elevation/ESRI_Elevation_World/MapServer/exts/ElevationsSOE/ElevationLayers/1/GetElevationData", - elevations: {}, //缓存的高程数据 - factor: 1, //高程缩放因子 - - //根据level获取包含level高程信息的ancestorElevationLevel - getAncestorElevationLevel(level: number) { - if (!(level >= 0)) { - throw "invalid level"; - } - var a = Math.floor((level - 1 - Kernel.ELEVATION_LEVEL) / 3); - var ancestor = Kernel.ELEVATION_LEVEL + 3 * a; - return ancestor; - }, - - /** - * 根据传入的extent以及行列数请求高程数据,返回(segment+1) * (segment+1)个数据,且乘积不能超过10000 - * 也就是说如果传递的是一个正方形的extent,那么segment最大取99,此处设置的segment是80 - */ - requestElevationsByTileGrid(level: number, row: number, column: number) { - if (!(level >= 0)) { - throw "invalid level"; - } - if (!(row >= 0)) { - throw "invalid row"; - } - if (!(column >= 0)) { - throw "invalid column"; - } - var segment = 80; - var name = level + "_" + row + "_" + column; - //只要elevations中有属性name,那么就表示该高程已经请求过或正在请求,这样就不要重新请求了 - //只有在完全没请求过的情况下去请求高程数据 - if (this.elevations.hasOwnProperty(name)) { - return; - } - this.elevations[name] = null; - var Eproj = MathUtils.getTileWebMercatorEnvelopeByGrid(level, row, column); - var minX: number = Eproj.minX; - var minY: number = Eproj.minY; - var maxX: number = Eproj.maxX; - var maxY: number = Eproj.maxY; - var gridWidth = (maxX - minX) / segment; - var gridHeight = (maxY - minY) / segment; - var a = gridWidth / 2; - var b = gridHeight / 2; - var extent = { - xmin: minX - a, - ymin: minY - b, - xmax: maxX + a, - ymax: maxY + b, - spatialReference: { - wkid: 102100 - } - }; - var strExtent = encodeURIComponent(JSON.stringify(extent)); - var rows = segment + 1; - var columns = segment + 1; - var f = "pjson"; - var args = "Extent=" + strExtent + "&Rows=" + rows + "&Columns=" + columns + "&f=" + f; - var xhr = new XMLHttpRequest(); - - function callback() { - if (xhr.readyState == 4 && xhr.status == 200) { - try { - var result = JSON.parse(xhr.responseText); - if (this.factor == 1) { - this.elevations[name] = result.data; - } else { - this.elevations[name] = Utils.map(this.elevations, function (item: number) { - return item * this.factor; - }.bind(this)); - } - } catch (e) { - console.error("requestElevationsByTileGrid_callback error", e); - } - } - } - xhr.onreadystatechange = callback.bind(this); - xhr.open("GET", "proxy.jsp?" + this.elevationUrl + "?" + args, true); - xhr.send(); - }, - - //无论怎样都尽量返回高程值,如果存在精确的高程,就获取精确高程;如果精确高程不存在,就返回上一个高程级别的估算高程 - //有可能 - getElevation(level: number, row: number, column: number): any { - if (!(level >= 0)) { - throw "invalid level"; - } - if (!(row >= 0)) { - throw "invalid row"; - } - if (!(column >= 0)) { - throw "invalid column"; - } - var result: any = null; - var exactResult = this.getExactElevation(level, row, column); - if (exactResult) { - //获取到准确高程 - result = exactResult; - } else { - //获取插值高程 - result = this.getLinearElevation(level, row, column); - } - return result; - }, - - //把>=8级的任意一个切片的tileGrid传进去,返回其高程值,该高程值是经过过滤了的,就是从大切片数据中抽吸出了其自身的高程信息 - //获取准确高程 - getExactElevation(level: number, row: number, column: number): any { - if (!(level >= 0)) { - throw "invalid level"; - } - if (!(row >= 0)) { - throw "invalid row"; - } - if (!(column >= 0)) { - throw "invalid column"; - } - var result: any = null; - var elevationLevel = this.getAncestorElevationLevel(level); - var elevationTileGrid = TileGrid.getTileGridAncestor(elevationLevel, level, row, column); - var elevationTileName = elevationTileGrid.level + "_" + elevationTileGrid.row + "_" + elevationTileGrid.column; - var ancestorElevations = this.elevations[elevationTileName]; - if (ancestorElevations instanceof Array && ancestorElevations.length > 0) { - if (level > Kernel.ELEVATION_LEVEL) { - //ltTileGridLevel表示level级别下位于Tile7左上角的TileGrid - var ltTileGridLevel = { - level: elevationTileGrid.level, - row: elevationTileGrid.row, - column: elevationTileGrid.column - }; //与level在同级别下但是在Tile7左上角的那个TileGrid - while (ltTileGridLevel.level != level) { - ltTileGridLevel = TileGrid.getTileGridByParent(ltTileGridLevel.level, ltTileGridLevel.row, ltTileGridLevel.column, MathUtils.LEFT_TOP); - } - if (ltTileGridLevel.level == level) { - //bigRow表示在level等级下当前grid距离左上角的grid的行数 - var bigRow = row - ltTileGridLevel.row; - //bigColumn表示在level等级下当前grid距离左上角的grid的列数 - var bigColumn = column - ltTileGridLevel.column; - var a = 81; //T7包含(80+1)*(80+1)个高程数据 - var deltaLevel = (elevationLevel + 3) - level; //当前level与T10相差的等级 - var deltaCount = Math.pow(2, deltaLevel); //一个当前tile能包含deltaCount*deltaCount个第10级的tile - //startSmallIndex表示该tile的左上角点在81*81的点阵中的索引号 - //bigRow*deltaCount表示当前切片距离T7最上面的切片的行包含了多少T10行,再乘以10表示跨过的高程点阵行数 - //bigColumn*deltaCount表示当前切片距离T7最左边的切片的列包含了多少T10列,再乘以10表示跨国的高程点阵列数 - var startSmallIndex = (bigRow * deltaCount * 10) * a + bigColumn * deltaCount * 10; - result = { - sourceLevel: elevationLevel, - elevations: [] - }; - for (var i = 0; i <= 10; i++) { - var idx = startSmallIndex; - for (var j = 0; j <= 10; j++) { - var ele = ancestorElevations[idx]; - result.elevations.push(ele); - idx += deltaCount; - } - //遍历完一行之后往下移,startSmallIndex表示一行的左边的起点 - startSmallIndex += deltaCount * a; - } - } - } - } - return result; - }, - - //获取线性插值的高程,比如要找E12的估算高程,那么就先找到E10的精确高程,E10的精确高程是从E7中提取的 - //即E7(81*81)->E10(11*11)->插值计算E11、E12、E13 - getLinearElevation(level: number, row: number, column: number): any { - if (!(level >= 0)) { - throw "invalid level"; - } - if (!(row >= 0)) { - throw "invalid row"; - } - if (!(column >= 0)) { - throw "invalid column"; - } - var result: any = null; - var elevationLevel = this.getAncestorElevationLevel(level); - var elevationTileGrid = TileGrid.getTileGridAncestor(elevationLevel, level, row, column); - var exactAncestorElevations = this.getExactElevation(elevationTileGrid.level, elevationTileGrid.row, elevationTileGrid.column); - var deltaLevel = level - elevationLevel; - if (exactAncestorElevations) { - result = { - sourceLevel: elevationLevel - 3, - elevations: null - }; - if (deltaLevel == 1) { - result.elevations = this.getLinearElevationFromParent(exactAncestorElevations, level, row, column); - } else if (deltaLevel == 2) { - result.elevations = this.getLinearElevationFromParent2(exactAncestorElevations, level, row, column); - } else if (deltaLevel == 3) { - result.elevations = this.getLinearElevationFromParent3(exactAncestorElevations, level, row, column); - } - } - return result; - }, - - //从直接父节点的高程数据中获取不是很准确的高程数据,比如T11从E10的高程中(10+1)*(10+1)中获取不是很准确的高程 - //通过线性插值的方式获取高程,不精确 - getLinearElevationFromParent(parentElevations: number[], level: number, row: number, column: number) { - if (!(parentElevations.length > 0)) { - throw "invalid parentElevations"; - } - if (!(level >= 0)) { - throw "invalid level"; - } - if (!(row >= 0)) { - throw "invalid row"; - } - if (!(column >= 0)) { - throw "invalid column"; - } - //position为切片在直接父切片中的位置 - var position = TileGrid.getTilePositionOfParent(level, row, column); - //先从parent中获取6个半行的数据 - var elevatios6_6:number[] = []; - var startIndex = 0; - if (position == MathUtils.LEFT_TOP) { - startIndex = 0; - } else if (position == MathUtils.RIGHT_TOP) { - startIndex = 5; - } else if (position == MathUtils.LEFT_BOTTOM) { - startIndex = 11 * 5; - } else if (position == MathUtils.RIGHT_BOTTOM) { - startIndex = 60; - } - var i:number, j:number, idx:number; - for (i = 0; i <= 5; i++) { - idx = startIndex; - for (j = 0; j <= 5; j++) { - var ele = parentElevations[idx]; - elevatios6_6.push(ele); - idx++; - } - //下移一行 - startIndex += 11; - } - //此时elevatios6_6表示的(5+1)*(5+1)的高程数据信息 - - var eleExact: number, eleExactTop: number, eleLinear:number, eleExactLeft: number; - //下面通过对每一行上的6个点数字两两取平均变成11个点数据 - var elevations6_11:number[] = []; - for (i = 0; i <= 5; i++) { - for (j = 0; j <= 5; j++) { - idx = 6 * i + j; - eleExact = elevatios6_6[idx]; - if (j > 0) { - eleExactLeft = elevatios6_6[idx - 1]; - eleLinear = (eleExactLeft + eleExact) / 2; - elevations6_11.push(eleLinear); - } - elevations6_11.push(eleExact); - } - } - //此时elevations6_11表示的是(5+1)*(10+1)的高程数据信息,对每行进行了线性插值 - - //下面要对每列进行线性插值,使得每列上的6个点数字两两取平均变成11个点数据 - var elevations11_11:number[] = []; - for (i = 0; i <= 5; i++) { - for (j = 0; j <= 10; j++) { - idx = 11 * i + j; - eleExact = elevations6_11[idx]; - if (i > 0) { - eleExactTop = elevations6_11[idx - 11]; - eleLinear = (eleExactTop + eleExact) / 2; - elevations11_11[(2 * i - 1) * 11 + j] = eleLinear; - } - elevations11_11[2 * i * 11 + j] = eleExact; - } - } - //此时elevations11_11表示的是(10+1)*(10+1)的高程数据信息 - - return elevations11_11; - }, - - //从相隔两级的高程中获取线性插值数据,比如从T10上面获取T12的高程数据 - //parent2Elevations是(10+1)*(10+1)的高程数据 - //level、row、column是子孙切片的信息 - getLinearElevationFromParent2(parent2Elevations: number[], level: number, row: number, column: number) { - var parentTileGrid = TileGrid.getTileGridAncestor(level - 1, level, row, column); - var parentElevations = this.getLinearElevationFromParent(parent2Elevations, parentTileGrid.level, parentTileGrid.row, parentTileGrid.column); - var elevations = this.getLinearElevationFromParent(parentElevations, level, row, column); - return elevations; - }, - - //从相隔三级的高程中获取线性插值数据,比如从T10上面获取T13的高程数据 - //parent3Elevations是(10+1)*(10+1)的高程数据 - //level、row、column是重孙切片的信息 - getLinearElevationFromParent3(parent3Elevations: number[], level: number, row: number, column: number) { - var parentTileGrid = TileGrid.getTileGridAncestor(level - 1, level, row, column); - var parentElevations = this.getLinearElevationFromParent2(parent3Elevations, parentTileGrid.level, parentTileGrid.row, parentTileGrid.column); - var elevations = this.getLinearElevationFromParent(parentElevations, level, row, column); - return elevations; - } -}; - -export = Elevation; \ No newline at end of file diff --git a/src/world/Enum.ts b/src/world/Enum.ts index 4e7f1a3..d79d72c 100644 --- a/src/world/Enum.ts +++ b/src/world/Enum.ts @@ -4,10 +4,6 @@ enum Enum { FULL_IN, FULL_OUT, IN_OUT, - NOKIA_TILED_MAP, - Google_TILED_MAP, - OSM_TILED_MAP, - BLENDED_TILED_MAP, GLOBE_TILE, TERRAIN_TILE } diff --git a/src/world/Event.ts b/src/world/Event.ts index 4a302b1..22e38d8 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -1,8 +1,8 @@ /// import Kernel = require("./Kernel"); -import MathUtils = require("./Math"); -import Vector = require("./Vector"); -import PerspectiveCamera = require("./PerspectiveCamera"); +import MathUtils = require("./math/Math"); +import Vector = require("./math/Vector"); +import Camera from "./Camera"; type MouseMoveListener = (e: MouseEvent) => {}; @@ -49,62 +49,63 @@ const EventModule = { }, onMouseDown(event: MouseEvent) { - if (Kernel.globe) { - this.bMouseDown = true; - this.previousX = event.layerX || event.offsetX; - this.previousY = event.layerY || event.offsetY; - var pickResult = Kernel.globe.camera.getPickCartesianCoordInEarthByCanvas(this.previousX, this.previousY); - if (pickResult.length > 0) { - this.dragGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); - console.log("单击点三维坐标:(" + pickResult[0].x + "," + pickResult[0].y + "," + pickResult[0].z + ");经纬度坐标:[" + this.dragGeo[0] + "," + this.dragGeo[1] + "]"); - } - this.canvas.addEventListener("mousemove", this.onMouseMoveListener, false); + var globe = Kernel.globe; + if (!globe || globe.isAnimating()) { + return; + } + this.bMouseDown = true; + this.previousX = event.layerX || event.offsetX; + this.previousY = event.layerY || event.offsetY; + var pickResult = Kernel.globe.camera.getPickCartesianCoordInEarthByCanvas(this.previousX, this.previousY); + if (pickResult.length > 0) { + this.dragGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); + //console.log("单击点三维坐标:(" + pickResult[0].x + "," + pickResult[0].y + "," + pickResult[0].z + ");经纬度坐标:[" + this.dragGeo[0] + "," + this.dragGeo[1] + "]"); } + this.canvas.addEventListener("mousemove", this.onMouseMoveListener, false); }, onMouseMove(event: MouseEvent) { var globe = Kernel.globe; - if (globe && this.bMouseDown) { - var currentX = event.layerX || event.offsetX; - var currentY = event.layerY || event.offsetY; - var pickResult = globe.camera.getPickCartesianCoordInEarthByCanvas(currentX, currentY); - if (pickResult.length > 0) { - //鼠标在地球范围内 - if (this.dragGeo) { - //鼠标拖动过程中要显示底图 - //globe.showAllSubTiledLayerAndTiles(); - var newGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); - this.moveGeo(this.dragGeo[0], this.dragGeo[1], newGeo[0], newGeo[1]); - } else { - //进入地球内部 - this.dragGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); - } - this.previousX = currentX; - this.previousY = currentY; - this.canvas.style.cursor = "pointer"; + if (!globe || globe.isAnimating() || !this.bMouseDown) { + return; + } + var currentX = event.layerX || event.offsetX; + var currentY = event.layerY || event.offsetY; + var pickResult = globe.camera.getPickCartesianCoordInEarthByCanvas(currentX, currentY); + if (pickResult.length > 0) { + //鼠标在地球范围内 + if (this.dragGeo) { + //鼠标拖动过程中要显示底图 + //globe.showAllSubTiledLayerAndTiles(); + var newGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); + this.moveGeo(this.dragGeo[0], this.dragGeo[1], newGeo[0], newGeo[1]); } else { - //鼠标超出地球范围 - this.previousX = -1; - this.previousY = -1; - this.dragGeo = null; - this.canvas.style.cursor = "default"; + //进入地球内部 + this.dragGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); } + this.previousX = currentX; + this.previousY = currentY; + this.canvas.style.cursor = "pointer"; + } else { + //鼠标超出地球范围 + this.previousX = -1; + this.previousY = -1; + this.dragGeo = null; + this.canvas.style.cursor = "default"; } }, moveGeo(oldLon: number, oldLat: number, newLon: number, newLat: number) { - if(oldLon === newLon && oldLat === newLat){ + if (oldLon === newLon && oldLat === newLat) { return; } - var p1 = MathUtils.geographicToCartesianCoord(oldLon, oldLat); + var p1 = MathUtils.geographicToCartesianCoord(oldLon, oldLat); var v1 = Vector.fromVertice(p1); - v1.normalize(); var p2 = MathUtils.geographicToCartesianCoord(newLon, newLat); var v2 = Vector.fromVertice(p2); - v2.normalize(); var rotateVector = v1.cross(v2); - var rotateRadian = -Math.acos(v1.dot(v2)); - var camera: PerspectiveCamera = Kernel.globe.camera; + var rotateRadian = -Vector.getRadianOfTwoVectors(v1, v2); + var camera: Camera = Kernel.globe.camera; camera.worldRotateByVector(rotateRadian, rotateVector); }, @@ -121,25 +122,27 @@ const EventModule = { onDbClick(event: MouseEvent) { var globe = Kernel.globe; - if (globe) { - var absoluteX = event.layerX || event.offsetX; - var absoluteY = event.layerY || event.offsetY; - var pickResult = globe.camera.getPickCartesianCoordInEarthByCanvas(absoluteX, absoluteY); - globe.setLevel(globe.CURRENT_LEVEL + 1); - if (pickResult.length >= 1) { - var pickVertice = pickResult[0]; - var lonlat = MathUtils.cartesianCoordToGeographic(pickVertice); - var lon = lonlat[0]; - var lat = lonlat[1]; - globe.setLevel(globe.CURRENT_LEVEL + 1); - this.moveLonLatToCanvas(lon, lat, absoluteX, absoluteY); - } + if (!globe || globe.isAnimating()) { + return; + } + + var absoluteX = event.layerX || event.offsetX; + var absoluteY = event.layerY || event.offsetY; + var pickResult = globe.camera.getPickCartesianCoordInEarthByCanvas(absoluteX, absoluteY); + globe.setLevel(globe.getLevel() + 1); + if (pickResult.length >= 1) { + var pickVertice = pickResult[0]; + var lonlat = MathUtils.cartesianCoordToGeographic(pickVertice); + var lon = lonlat[0]; + var lat = lonlat[1]; + globe.setLevel(globe.getLevel() + 1); + this.moveLonLatToCanvas(lon, lat, absoluteX, absoluteY); } }, onMouseWheel(event: MouseWheelEvent) { var globe = Kernel.globe; - if (!globe) { + if (!globe || globe.isAnimating()) { return; } @@ -154,57 +157,35 @@ const EventModule = { delta = event.detail; deltaLevel = -parseInt((delta / 3)); } - var newLevel = globe.CURRENT_LEVEL + deltaLevel; - if(newLevel >= 0){ + var newLevel = globe.getLevel() + deltaLevel; + if (newLevel >= 0) { //globe.setLevel(newLevel); globe.animateToLevel(newLevel); - } + } }, + /** + * 通过向上和向下的键盘按键调整Camera视线方向的倾斜角度pitch + * 初始pitch值为0 + */ onKeyDown(event: KeyboardEvent) { var globe = Kernel.globe; - if (!globe) { + if (!globe || globe.isAnimating()) { return; } - var MIN_PITCH = 36; var DELTA_PITCH = 2; var camera = globe.camera; var keyNum = event.keyCode !== undefined ? event.keyCode : event.which; //上、下、左、右:38、40、37、39 - if (keyNum == 38 || keyNum == 40) { - if (keyNum == 38) { - if (camera.pitch <= MIN_PITCH) { - return; - } - } else if (keyNum == 40) { - if (camera.pitch >= 90) { - return; - } - DELTA_PITCH *= -1; - } - - var pickResult = camera.getDirectionIntersectPointWithEarth(); - if (pickResult.length > 0) { - var pIntersect = pickResult[0]; - var pCamera = camera.getPosition(); - var legnth2Intersect = MathUtils.getLengthFromVerticeToVertice(pCamera, pIntersect); - var mat = camera.matrix.clone(); - mat.setColumnTrans(pIntersect.x, pIntersect.y, pIntersect.z); - var DELTA_RADIAN = MathUtils.degreeToRadian(DELTA_PITCH); - mat.localRotateX(DELTA_RADIAN); - var dirZ = mat.getColumnZ(); - dirZ.setLength(legnth2Intersect); - var pNew = Vector.verticePlusVector(pIntersect, dirZ); - camera.look(pNew, pIntersect); - camera.pitch -= DELTA_PITCH; - globe.refresh(); - } else { - alert("视线与地球无交点"); - } + if (keyNum === 38) { + //向上键 + camera.setDeltaPitch(DELTA_PITCH); + } else if (keyNum === 40) { + //向下键 + camera.setDeltaPitch(-DELTA_PITCH); } } - }; export = EventModule; \ No newline at end of file diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 7d98204..f71e23a 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -1,45 +1,38 @@ /// import Kernel = require("./Kernel"); import Utils = require("./Utils"); -import ShaderContent = require("./ShaderContent"); import Renderer = require("./Renderer"); -import PerspectiveCamera = require("./PerspectiveCamera"); +import Camera, { CameraCore } from "./Camera"; import Scene = require("./Scene"); -import TiledLayer = require("./TiledLayer"); -import SubTiledLayer = require("./SubTiledLayer"); -import Tile = require("./Tile"); +import TiledLayer = require("./layers/TiledLayer"); import ImageUtils = require("./Image"); import EventUtils = require("./Event"); class Globe { - MAX_LEVEL: number = 15;//最大的渲染级别15 - CURRENT_LEVEL: number = -1; //当前渲染等级 - REFRESH_INTERVAL: number = 300; //Globe自动刷新时间间隔,以毫秒为单位 - idTimeOut: any = null; //refresh自定刷新的timeOut的handle + private readonly REFRESH_INTERVAL: number = 100; //Globe自动刷新时间间隔,以毫秒为单位 + // private idTimeOut: number = -1; //refresh自定刷新的timeOut的handle renderer: Renderer = null; scene: Scene = null; - camera: PerspectiveCamera = null; + camera: Camera = null; tiledLayer: TiledLayer = null; + private cameraCore: CameraCore = null; - constructor(canvas: HTMLCanvasElement, args: any) { - args = args || {}; + constructor(canvas: HTMLCanvasElement) { Kernel.globe = this; - - var vs_content = ShaderContent.SIMPLE_SHADER.VS_CONTENT; - var fs_content = ShaderContent.SIMPLE_SHADER.FS_CONTENT; - this.renderer = Kernel.renderer = new Renderer(canvas, vs_content, fs_content); + this.renderer = new Renderer(canvas); this.scene = new Scene(); var radio = canvas.width / canvas.height; - this.camera = new PerspectiveCamera(30, radio, 1.0, 20000000.0); - this.renderer.bindScene(this.scene); - this.renderer.bindCamera(this.camera); + this.camera = new Camera(30, radio, 1, Kernel.EARTH_RADIUS * 3); + this.renderer.setScene(this.scene); + this.renderer.setCamera(this.camera); this.setLevel(0); this.renderer.setIfAutoRefresh(true); EventUtils.initLayout(); + this._tick(); } setTiledLayer(tiledLayer: TiledLayer) { - clearTimeout(this.idTimeOut); + // clearTimeout(this.idTimeOut); //在更换切片图层的类型时清空缓存的图片 ImageUtils.clear(); if (this.tiledLayer) { @@ -50,146 +43,66 @@ class Globe { this.scene.tiledLayer = null; } this.tiledLayer = tiledLayer; - this.scene.add(this.tiledLayer); - //添加第0级的子图层 - var subLayer0 = new SubTiledLayer({ - level: 0 - }); - this.tiledLayer.add(subLayer0); + this.scene.add(this.tiledLayer, true); + this.refresh(true); + } - //要对level为1的图层进行特殊处理,在创建level为1时就创建其中的全部的四个tile - var subLayer1 = new SubTiledLayer({ - level: 1 - }); - this.tiledLayer.add(subLayer1); - Kernel.canvas.style.cursor = "wait"; - for (var m = 0; m <= 1; m++) { - for (var n = 0; n <= 1; n++) { - var args = { - level: 1, - row: m, - column: n, - url: "" - }; - args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); - var tile = new Tile(args); - subLayer1.add(tile); - } - } - Kernel.canvas.style.cursor = "default"; - this.tick(); + getLevel() { + return this.camera ? this.camera.getLevel() : -1; } setLevel(level: number) { - if (!Utils.isNonNegativeInteger(level)) { - throw "invalid level:" + level; - } - - level = level > this.MAX_LEVEL ? this.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 - if (level != this.CURRENT_LEVEL) { - if (this.camera instanceof PerspectiveCamera) { - //要先执行camera.setLevel,然后再刷新 - this.camera.setLevel(level); - this.refresh(); - } + if (this.camera) { + this.camera.setLevel(level); } } - isAnimating(): boolean{ + isAnimating(): boolean { return this.camera.isAnimating(); } - animateToLevel(level: number){ - if(!this.isAnimating()){ - this.camera.animateToLevel(level); - } + animateToLevel(level: number) { + if (!this.isAnimating()) { + level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 + if (level !== this.getLevel()) { + this.camera.animateToLevel(level); + } + } } - /** - * 返回当前的各种矩阵信息:视点矩阵、投影矩阵、两者乘积,以及前三者的逆矩阵 - * @returns {{View: null, _View: null, Proj: null, _Proj: null, ProjView: null, _View_Proj: null}} - * @private - */ - /*_getMatrixInfo() { - var options: any = { - View: null, //视点矩阵 - _View: null, //视点矩阵的逆矩阵 - Proj: null, //投影矩阵 - _Proj: null, //投影矩阵的逆矩阵 - ProjView: null, //投影矩阵与视点矩阵的乘积 - _View_Proj: null //视点逆矩阵与投影逆矩阵的乘积 - }; - options.View = this.getViewMatrix(); - options._View = options.View.getInverseMatrix(); - options.Proj = this.projMatrix; - options._Proj = options.Proj.getInverseMatrix(); - options.ProjView = options.Proj.multiplyMatrix(options.View); - options._View_Proj = options.ProjView.getInverseMatrix(); - return options; - }*/ - - tick() { - var globe = Kernel.globe; - if (globe) { - globe.refresh(); - this.idTimeOut = setTimeout(globe.tick, globe.REFRESH_INTERVAL); + private _tick() { + try { + //如果refresh方法出现异常而且没有捕捉,那么就会导致无法继续设置setTimeout,从而无法进一步更新切片 + this.refresh(); + } catch (e) { + console.error(e); } + setTimeout(() => { + this._tick(); + }, this.REFRESH_INTERVAL); } - refresh() { + refresh(force: boolean = false) { if (!this.tiledLayer || !this.scene || !this.camera) { return; } - var level = this.CURRENT_LEVEL + 3; - this.tiledLayer.updateSubLayerCount(level); - var projView = this.camera.getProjViewMatrix(); + //先更新camera中的各种矩阵 + this.camera.update(force); + var newCameraCore = this.camera.getCameraCore(); + var isNeedRefresh = force || !newCameraCore.equals(this.cameraCore); + this.cameraCore = newCameraCore; + if (!isNeedRefresh) { + return; + } + var lastLevel = this.getLevel() + 3; var options = { - projView: projView, threshold: 1 }; - options.threshold = Math.min(90 / this.camera.pitch, 1.5); + var pitch = this.camera.getPitch(); + options.threshold = Math.min(90 / (90 - pitch), 1.5); //最大级别的level所对应的可见TileGrids - var lastLevelTileGrids = this.camera.getVisibleTilesByLevel(level, options); - var levelsTileGrids: any[] = []; //level-2 - var parentTileGrids = lastLevelTileGrids; - var i: number; - for (i = level; i >= 2; i--) { - levelsTileGrids.push(parentTileGrids); //此行代码表示第i层级的可见切片 - parentTileGrids = Utils.map(parentTileGrids, function (item) { - return item.getParent(); - }); - parentTileGrids = Utils.filterRepeatArray(parentTileGrids); - } - levelsTileGrids.reverse(); //2-level - for (i = 2; i <= level; i++) { - var subLevel = i; - var subLayer = this.tiledLayer.children[subLevel]; - subLayer.updateTiles(levelsTileGrids[0], true); - levelsTileGrids.splice(0, 1); - } - if (Kernel.TERRAIN_ENABLED) { - this.requestElevationsAndCheckTerrain(); - } - } - - //请求更新高程数据,并检测Terrain - requestElevationsAndCheckTerrain() { - var level = this.tiledLayer.children.length - 1; - //当level>7时请求更新高程数据 - //请求的数据与第7级的切片大小相同 - //if(level > Kernel.ELEVATION_LEVEL){ - - //达到TERRAIN_LEVEL级别时考虑三维请求 - if (level >= Kernel.TERRAIN_LEVEL) { - for (var i = Kernel.ELEVATION_LEVEL + 1; i <= level; i++) { - var subLayer = this.tiledLayer.children[i]; - subLayer.requestElevations(); - //检查SubTiledLayer下的子图层是否符合转换成TerrainTile的条件,如果适合就自动以三维地形图显示 - if (i >= Kernel.TERRAIN_LEVEL) { - subLayer.checkTerrain(); - } - } - } + var lastLevelTileGrids = this.camera.getVisibleTilesByLevel(lastLevel, options); + this.tiledLayer.refresh(lastLevel, lastLevelTileGrids); } } diff --git a/src/world/GraphicGroup.ts b/src/world/GraphicGroup.ts new file mode 100644 index 0000000..67f49a6 --- /dev/null +++ b/src/world/GraphicGroup.ts @@ -0,0 +1,83 @@ +/// +import Kernel = require("./Kernel"); +import Graphic = require("./graphics/Graphic"); +import Camera from "./Camera"; + +type Drawable = Graphic | GraphicGroup; + +class GraphicGroup{ + id: number; + parent: GraphicGroup; + children: Drawable[]; + visible: boolean = true; + + constructor(){ + this.id = ++Kernel.idCounter; + this.children = []; + } + + add(g: Drawable, first: boolean = false){ + if(first){ + this.children.unshift(g); + }else{ + this.children.push(g); + } + g.parent = this; + } + + remove(g: Drawable): boolean{ + var result = false; + var findResult = this.findGraphicById(g.id); + if(findResult){ + g.destroy(); + this.children.splice(findResult.index, 1); + g = null; + result = true; + } + return result; + } + + clear(){ + var i = 0, length = this.children.length, g:Drawable = null; + for(; i < length; i++){ + g = this.children[i]; + g.destroy(); + } + this.children = []; + } + + destroy(){ + this.parent = null; + this.clear(); + } + + findGraphicById(graphicId: number){ + var i = 0, length = this.children.length, g:Drawable = null; + for(; i < length; i++){ + g = this.children[i]; + if(g.id === graphicId){ + return { + index: i, + graphic: g + }; + } + } + return null; + } + + isDrawable(){ + return this.visible; + } + + draw(camera: Camera){ + if(this.isDrawable()){ + this.children.forEach(function(g: Drawable){ + if(g.isDrawable()){ + g.draw(camera); + } + }); + } + } +} + +export = GraphicGroup; \ No newline at end of file diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index 6eecd2c..5f92154 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -3,20 +3,19 @@ import {WebGLRenderingContextExtension} from './Definitions'; import Globe = require("./Globe"); import Renderer = require("./Renderer"); +const radius = 500;//6378137 +const maxProjectedCoord = Math.PI * radius; + const Kernel = { - gl: null, + gl: null, canvas: null, - renderer: null, - globe: null, - idCounter: 0, //Object3D对象的唯一标识 - BASE_LEVEL: 6, //渲染的基准层级 - EARTH_RADIUS: 6378137, - MAX_PROJECTED_COORD: 20037508.3427892, - ELEVATION_LEVEL: 7, //开始获取高程数据 - TERRAIN_LEVEL: 10, //开始显示三维地形 - TERRAIN_ENABLED: false, //是否启用三维地形 - TERRAIN_PITCH: 80, //开始显示三维地形的pich - proxy: "" + globe: null, + idCounter: 0, //Object3D对象的唯一标识 + BASE_LEVEL: 6, //渲染的基准层级 + MAX_LEVEL: 15,//最大的渲染级别 + EARTH_RADIUS: radius, + MAX_PROJECTED_COORD: maxProjectedCoord, + proxy: "" }; export = Kernel; \ No newline at end of file diff --git a/src/world/Object3D.ts b/src/world/Object3D.ts index 1d02bd8..8e64ee5 100644 --- a/src/world/Object3D.ts +++ b/src/world/Object3D.ts @@ -1,236 +1,121 @@ /// import Kernel = require('./Kernel'); -import Matrix = require('./Matrix'); -import Vertice = require('./Vertice'); -import Vector = require('./Vector'); -import TextureMaterial = require('./TextureMaterial'); +import Matrix = require('./math/Matrix'); +import Vertice = require('./math/Vertice'); +import Vector = require('./math/Vector'); class Object3D { - id: number; - matrix: Matrix; - parent: any; - vertices: number[]; - vertexBuffer: WebGLBuffer; - indices: number[]; - indexBuffer: WebGLBuffer; - textureCoords: number[]; - textureCoordBuffer: WebGLBuffer; - material: TextureMaterial; - visible: boolean; - - constructor(args?: any) { - this.id = ++Kernel.idCounter; - this.matrix = new Matrix(); - this.parent = null; - this.vertices = []; - this.vertexBuffer = null; - this.indices = []; - this.indexBuffer = null; - this.textureCoords = []; - this.textureCoordBuffer = null; - this.material = null; - this.visible = true; - if (args && args.material) { - this.material = args.material; - } - this.createVerticeData(args); - } - - /** - * 根据传入的参数生成vertices和indices,然后通过调用setBuffers初始化buffer - * @param params 传入的参数 - */ - createVerticeData(params: any): void { - /*var infos = { - vertices:vertices, - indices:indices - }; - this.setBuffers(infos);*/ - } - - /** - * 设置buffer,由createVerticeData函数调用 - * @param infos 包含vertices和indices信息,由createVerticeData传入参数 - */ - setBuffers(infos: any): void { - if (infos) { - this.vertices = infos.vertices || []; - this.indices = infos.indices || []; - this.textureCoords = infos.textureCoords || []; - if (this.vertices.length > 0 && this.indices.length > 0) { - if (!(Kernel.gl.isBuffer(this.vertexBuffer))) { - this.vertexBuffer = Kernel.gl.createBuffer(); - } - Kernel.gl.bindBuffer(Kernel.gl.ARRAY_BUFFER, this.vertexBuffer); - Kernel.gl.bufferData(Kernel.gl.ARRAY_BUFFER, new Float32Array(this.vertices), Kernel.gl.STATIC_DRAW); - - if (!(Kernel.gl.isBuffer(this.indexBuffer))) { - this.indexBuffer = Kernel.gl.createBuffer(); - } - Kernel.gl.bindBuffer(Kernel.gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - Kernel.gl.bufferData(Kernel.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), Kernel.gl.STATIC_DRAW); - } - - //使用纹理 - if (this.material instanceof TextureMaterial) { - if (this.textureCoords.length > 0) { //提供了纹理坐标 - if (!(Kernel.gl.isBuffer(this.textureCoordBuffer))) { - this.textureCoordBuffer = Kernel.gl.createBuffer(); - } - Kernel.gl.bindBuffer(Kernel.gl.ARRAY_BUFFER, this.textureCoordBuffer); - Kernel.gl.bufferData(Kernel.gl.ARRAY_BUFFER, new Float32Array(this.textureCoords), Kernel.gl.STATIC_DRAW); - } - } - } - Kernel.gl.bindBuffer(Kernel.gl.ARRAY_BUFFER, null); - Kernel.gl.bindBuffer(Kernel.gl.ELEMENT_ARRAY_BUFFER, null); - } - - setShaderMatrix(camera: any): void { - // if (!(camera instanceof PerspectiveCamera)) { - // throw "invalid camera : not World.PerspectiveCamera"; - // } - camera.viewMatrix = (camera.viewMatrix instanceof Matrix) ? camera.viewMatrix : camera.getViewMatrix(); - var mvMatrix = camera.viewMatrix.multiplyMatrix(this.matrix); - Kernel.gl.uniformMatrix4fv(Kernel.gl.shaderProgram.uMVMatrix, false, mvMatrix.elements); - Kernel.gl.uniformMatrix4fv(Kernel.gl.shaderProgram.uPMatrix, false, camera.projMatrix.elements); - } - - draw(camera: any): void { - // if (!(camera instanceof PerspectiveCamera)) { - // throw "invalid camera : not World.PerspectiveCamera"; - // } - if (this.visible) { - if (this.material instanceof TextureMaterial && this.material.loaded) { - Kernel.gl.enableVertexAttribArray(Kernel.gl.shaderProgram.aTextureCoord); - Kernel.gl.bindBuffer(Kernel.gl.ARRAY_BUFFER, this.textureCoordBuffer); - Kernel.gl.vertexAttribPointer(Kernel.gl.shaderProgram.aTextureCoord, 2, Kernel.gl.FLOAT, false, 0, 0); - - Kernel.gl.activeTexture(Kernel.gl.TEXTURE0); - Kernel.gl.bindTexture(Kernel.gl.TEXTURE_2D, this.material.texture); - Kernel.gl.uniform1i(Kernel.gl.shaderProgram.uSampler, 0); - - this.setShaderMatrix(camera); - - //往shader中对vertex赋值 - Kernel.gl.enableVertexAttribArray(Kernel.gl.shaderProgram.aVertexPosition); - Kernel.gl.bindBuffer(Kernel.gl.ARRAY_BUFFER, this.vertexBuffer); - Kernel.gl.vertexAttribPointer(Kernel.gl.shaderProgram.aVertexPosition, 3, Kernel.gl.FLOAT, false, 0, 0); - - //设置索引,但不用往shader中赋值 - Kernel.gl.bindBuffer(Kernel.gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - //绘图 - Kernel.gl.drawElements(Kernel.gl.TRIANGLES, this.indices.length, Kernel.gl.UNSIGNED_SHORT, 0); - - Kernel.gl.bindBuffer(Kernel.gl.ARRAY_BUFFER, null); - Kernel.gl.bindBuffer(Kernel.gl.ELEMENT_ARRAY_BUFFER, null); - Kernel.gl.bindTexture(Kernel.gl.TEXTURE_2D, null); - } - } - } - - //释放显存中的buffer资源 - releaseBuffers(): void { - //释放显卡中的资源 - if (Kernel.gl.isBuffer(this.vertexBuffer)) { - Kernel.gl.deleteBuffer(this.vertexBuffer); - } - if (Kernel.gl.isBuffer(this.indexBuffer)) { - Kernel.gl.deleteBuffer(this.indexBuffer); - } - if (Kernel.gl.isBuffer(this.textureCoordBuffer)) { - Kernel.gl.deleteBuffer(this.textureCoordBuffer); - } - this.vertexBuffer = null; - this.indexBuffer = null; - this.textureCoordBuffer = null; - } - - destroy(): void { - this.parent = null; - this.releaseBuffers(); - if (this.material instanceof TextureMaterial) { - this.material.releaseTexture(); - this.material = null; - } - } - - //需要子类重写 - getPosition(): Vertice { - var position = this.matrix.getPosition(); - return position; + protected matrix: Matrix; + + constructor() { + this.matrix = new Matrix(); + } + + getMatrix(): Matrix{ + return this.matrix; + } + + cloneMatrix(): Matrix{ + return this.matrix.clone(); } - //需要子类重写 - setPosition(x: number, y: number, z: number): void { - this.matrix.setColumnTrans(x, y, z); + setVectorX(vector: Vector) { + this.matrix.setVectorX(vector); + } + + getVectorX(): Vector { + return this.matrix.getVectorX(); + } + + setVectorY(vector: Vector) { + this.matrix.setVectorY(vector); + } + + getVectorY(): Vector { + return this.matrix.getVectorY(); + } + + setVectorZ(vector: Vector) { + this.matrix.setVectorZ(vector); + } + + getVectorZ(): Vector { + return this.matrix.getVectorZ(); + } + + setPosition(vertice: Vertice): void { + this.matrix.setPosition(vertice); + } + + getPosition(): Vertice { + return this.matrix.getPosition(); } worldTranslate(x: number, y: number, z: number): void { - this.matrix.worldTranslate(x, y, z); + this.matrix.worldTranslate(x, y, z); } localTranslate(x: number, y: number, z: number): void { - this.matrix.localTranslate(x, y, z); + this.matrix.localTranslate(x, y, z); } worldScale(scaleX: number, scaleY: number, scaleZ: number): void { - this.matrix.worldScale(scaleX, scaleY, scaleZ); + this.matrix.worldScale(scaleX, scaleY, scaleZ); } localScale(scaleX: number, scaleY: number, scaleZ: number): void { - this.matrix.localScale(scaleX, scaleY, scaleZ); + this.matrix.localScale(scaleX, scaleY, scaleZ); } worldRotateX(radian: number): void { - this.matrix.worldRotateX(radian); + this.matrix.worldRotateX(radian); } worldRotateY(radian: number): void { - this.matrix.worldRotateY(radian); + this.matrix.worldRotateY(radian); } worldRotateZ(radian: number): void { - this.matrix.worldRotateZ(radian); + this.matrix.worldRotateZ(radian); } worldRotateByVector(radian: number, vector: Vector): void { - this.matrix.worldRotateByVector(radian, vector); + this.matrix.worldRotateByVector(radian, vector); } localRotateX(radian: number): void { - this.matrix.localRotateX(radian); + this.matrix.localRotateX(radian); } localRotateY(radian: number): void { - this.matrix.localRotateY(radian); + this.matrix.localRotateY(radian); } localRotateZ(radian: number): void { - this.matrix.localRotateZ(radian); + this.matrix.localRotateZ(radian); } //localVector指的是相对于模型坐标系中的向量 localRotateByVector(radian: number, localVector: Vector): void { - this.matrix.localRotateByVector(radian, localVector); + this.matrix.localRotateByVector(radian, localVector); } getXAxisDirection(): Vector { - var directionX = this.matrix.getColumnX(); - directionX.normalize(); - return directionX; + var directionX = this.matrix.getVectorX(); + directionX.normalize(); + return directionX; } getYAxisDirection(): Vector { - var directionY = this.matrix.getColumnY(); - directionY.normalize(); - return directionY; + var directionY = this.matrix.getVectorY(); + directionY.normalize(); + return directionY; } getZAxisDirection(): Vector { - var directionZ = this.matrix.getColumnZ(); - directionZ.normalize(); - return directionZ; + var directionZ = this.matrix.getVectorZ(); + directionZ.normalize(); + return directionZ; } } diff --git a/src/world/Object3DComponents.ts b/src/world/Object3DComponents.ts deleted file mode 100644 index 39424b1..0000000 --- a/src/world/Object3DComponents.ts +++ /dev/null @@ -1,139 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Vector = require('./Vector'); -import Matrix = require('./Matrix'); -import Object3D = require('./Object3D'); -import PerspectiveCamera = require('./PerspectiveCamera'); - -type ChildType = Object3D | Object3DComponents; - -//三维对象集合 -class Object3DComponents { - id: number; - matrix: Matrix; - visible: boolean; - parent: any; - children: ChildType[]; - - constructor() { - this.id = ++Kernel.idCounter; - this.matrix = new Matrix(); - this.visible = true; - this.parent = null; - this.children = []; - } - - add(obj: ChildType) { - if (this.findObjById(obj.id) !== null) { - console.debug("obj已经存在于Object3DComponents中,无法将其再次加入!"); - return; - } else { - this.children.push(obj); - obj.parent = this; - } - } - - remove(obj: ChildType) { - if (obj) { - var result = this.findObjById(obj.id); - if (result === null) { - console.debug("obj不存在于Object3DComponents中,所以无法将其从中删除!"); - return false; - } - obj.destroy(); - this.children.splice(result.index, 1); - obj = null; - return true; - } else { - return false; - } - } - - //销毁所有的子节点 - clear() { - for (var i = 0; i < this.children.length; i++) { - var obj = this.children[i]; - obj.destroy(); - } - this.children = []; - } - - //销毁自身及其子节点 - destroy() { - this.parent = null; - this.clear(); - } - - findObjById(objId: number): any { - for (var i = 0; i < this.children.length; i++) { - var obj = this.children[i]; - if (obj.id == objId) { - (obj).index = i; - return obj; - } - } - return null; - } - - draw(camera: PerspectiveCamera) { - for (var i = 0; i < this.children.length; i++) { - var obj = this.children[i]; - if (obj) { - if (obj.visible) { - (obj).draw(camera); - } - } - } - } - - worldTranslate(x: number, y: number, z: number) { - this.matrix.worldTranslate(x, y, z); - } - - localTranslate(x: number, y: number, z: number) { - this.matrix.localTranslate(x, y, z); - } - - worldScale(scaleX: number, scaleY: number, scaleZ: number) { - this.matrix.worldScale(scaleX, scaleY, scaleZ); - } - - localScale(scaleX: number, scaleY: number, scaleZ: number) { - this.matrix.localScale(scaleX, scaleY, scaleZ); - } - - worldRotateX(radian: number) { - this.matrix.worldRotateX(radian); - } - - worldRotateY(radian: number) { - this.matrix.worldRotateY(radian); - } - - worldRotateZ(radian: number) { - this.matrix.worldRotateZ(radian); - } - - worldRotateByVector(radian: number, vector: Vector) { - this.matrix.worldRotateByVector(radian, vector); - } - - localRotateX(radian: number) { - this.matrix.localRotateX(radian); - } - - localRotateY(radian: number) { - this.matrix.localRotateY(radian); - } - - localRotateZ(radian: number) { - this.matrix.localRotateZ(radian); - } - - //localVector指的是相对于模型坐标系中的向量 - localRotateByVector(radian: number, localVector: Vector) { - this.matrix.localRotateByVector(radian, localVector); - } -} - -export = Object3DComponents; \ No newline at end of file diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts deleted file mode 100644 index 2d90c33..0000000 --- a/src/world/PerspectiveCamera.ts +++ /dev/null @@ -1,715 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Utils = require('./Utils'); -import MathUtils = require('./Math'); -import Vertice = require('./Vertice'); -import Vector = require('./Vector'); -import Line = require('./Line'); -import Plan = require('./Plan'); -import TileGrid = require('./TileGrid'); -import Matrix = require('./Matrix'); -import Object3D = require('./Object3D'); -import Globe = require('./Globe');//just used for TypeScript validate type - -class PerspectiveCamera extends Object3D { - pitch: number; - viewMatrix: Matrix; - projMatrix: Matrix; - Enum: any = { - EARTH_FULL_OVERSPREAD_SCREEN: "EARTH_FULL_OVERSPREAD_SCREEN", //Canvas内全部被地球充满 - EARTH_NOT_FULL_OVERSPREAD_SCREEN: "EARTH_NOT_FULL_OVERSPREAD_SCREEN" //Canvas没有全部被地球充满 - }; - private animating: boolean = false; - - constructor(public fov = 90, public aspect = 1, public near = 1, public far = 1) { - super(null); - this.pitch = 90; - this.projMatrix = new Matrix(); - this.setPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); - } - - setPerspectiveMatrix(fov: number = 90, aspect: number = 1, near: number = 1, far: number = 1): void { - this.fov = fov; - this.aspect = aspect; - this.near = near; - this.far = far; - var mat = [1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ]; - var halfFov = this.fov * Math.PI / 180 / 2; - var a = 1 / Math.tan(halfFov); - var b = this.far - this.near; - - mat[0] = a / this.aspect; - mat[5] = a; - mat[10] = -(this.far + this.near) / b; - mat[11] = -1; - mat[14] = -2 * this.near * this.far / b; - mat[15] = 0; - - //by comparision with matrixProjection.exe and glMatrix, - //the 11th element is always -1 - this.projMatrix.setElements( - mat[0], mat[1], mat[2], mat[3], - mat[4], mat[5], mat[6], mat[7], - mat[8], mat[9], mat[10], mat[11], - mat[12], mat[13], mat[14], mat[15] - ); - } - - getLightDirection(): Vector { - var dirVertice = this.matrix.getColumnZ(); - var direction = new Vector(-dirVertice.x, -dirVertice.y, -dirVertice.z); - direction.normalize(); - return direction; - } - - //获取投影矩阵与视点矩阵的乘积 - getProjViewMatrix(): Matrix { - var viewMatrix = this.getViewMatrix(); - var projViewMatrix = this.projMatrix.multiplyMatrix(viewMatrix); - return projViewMatrix; - } - - setFov(fov: number): void { - if (!(fov > 0)) { - throw "invalid fov:" + fov; - } - this.setPerspectiveMatrix(fov, this.aspect, this.near, this.far); - } - - setAspect(aspect: number): void { - if (!(aspect > 0)) { - throw "invalid aspect:" + aspect; - } - this.setPerspectiveMatrix(this.fov, aspect, this.near, this.far); - } - - setNear(near: number): void { - if (!(near > 0)) { - throw "invalid near:" + near; - } - this.setPerspectiveMatrix(this.fov, this.aspect, near, this.far); - } - - setFar(far: number): void { - if (!(far > 0)) { - throw "invalid far:" + far; - } - this.setPerspectiveMatrix(this.fov, this.aspect, this.near, far); - } - - getViewMatrix(): Matrix { - //视点矩阵是camera的模型矩阵的逆矩阵 - return this.matrix.getInverseMatrix(); - } - - look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { - var cameraPntCopy = cameraPnt.clone(); - var targetPntCopy = targetPnt.clone(); - var up = upDirection.clone(); - var transX = cameraPntCopy.x; - var transY = cameraPntCopy.y; - var transZ = cameraPntCopy.z; - var zAxis = new Vector(cameraPntCopy.x - targetPntCopy.x, cameraPntCopy.y - targetPntCopy.y, cameraPntCopy.z - targetPntCopy.z).normalize(); - var xAxis = up.cross(zAxis).normalize(); - var yAxis = zAxis.cross(xAxis).normalize(); - - this.matrix.setColumnX(xAxis.x, xAxis.y, xAxis.z); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置X轴方向 - this.matrix.setColumnY(yAxis.x, yAxis.y, yAxis.z); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置Y轴方向 - this.matrix.setColumnZ(zAxis.x, zAxis.y, zAxis.z); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置Z轴方向 - this.matrix.setColumnTrans(transX, transY, transZ); //此处相当于对Camera的模型矩阵(不是视点矩阵)设置偏移量 - this.matrix.setLastRowDefault(); - - var deltaX = cameraPntCopy.x - targetPntCopy.x; - var deltaY = cameraPntCopy.y - targetPntCopy.y; - var deltaZ = cameraPntCopy.z - targetPntCopy.z; - var far = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ); - this.setFar(far); - } - - lookAt(targetPnt: Vertice, upDirection?: Vector): void { - var targetPntCopy = targetPnt.clone(); - var position = this.getPosition(); - this.look(position, targetPntCopy, upDirection); - } - - //点变换: World->NDC - convertVerticeFromWorldToNDC(verticeInWorld: Vertice, /*optional*/ projViewMatrix?: Matrix): Vertice { - if (!(projViewMatrix instanceof Matrix)) { - projViewMatrix = this.getProjViewMatrix(); - } - var columnWorld = [verticeInWorld.x, verticeInWorld.y, verticeInWorld.z, 1]; - var columnProject = projViewMatrix.multiplyColumn(columnWorld); - var w = columnProject[3]; - var columnNDC: number[] = []; - columnNDC[0] = columnProject[0] / w; - columnNDC[1] = columnProject[1] / w; - columnNDC[2] = columnProject[2] / w; - columnNDC[3] = 1; - var verticeInNDC = new Vertice(columnNDC[0], columnNDC[1], columnNDC[2]); - return verticeInNDC; - } - - //点变换: NDC->World - convertVerticeFromNdcToWorld(verticeInNDC: Vertice): Vertice { - var columnNDC: number[] = [verticeInNDC.x, verticeInNDC.y, verticeInNDC.z, 1]; //NDC归一化坐标 - var inverseProj = this.projMatrix.getInverseMatrix(); //投影矩阵的逆矩阵 - var columnCameraTemp = inverseProj.multiplyColumn(columnNDC); //带引号的“视坐标” - var cameraX = columnCameraTemp[0] / columnCameraTemp[3]; - var cameraY = columnCameraTemp[1] / columnCameraTemp[3]; - var cameraZ = columnCameraTemp[2] / columnCameraTemp[3]; - var cameraW = 1; - var columnCamera = [cameraX, cameraY, cameraZ, cameraW]; //真实的视坐标 - - var viewMatrix = this.getViewMatrix(); - var inverseView = viewMatrix.getInverseMatrix(); //视点矩阵的逆矩阵 - var columnWorld = inverseView.multiplyColumn(columnCamera); //单击点的世界坐标 - var verticeInWorld = new Vertice(columnWorld[0], columnWorld[1], columnWorld[2]); - return verticeInWorld; - } - - //点变换: Camera->World - convertVerticeFromCameraToWorld(verticeInCamera: Vertice, /*optional*/ viewMatrix?: Matrix): Vertice { - if (!(viewMatrix instanceof Matrix)) { - viewMatrix = this.getViewMatrix(); - } - var verticeInCameraCopy = verticeInCamera.clone(); - var inverseMatrix = viewMatrix.getInverseMatrix(); - var column = [verticeInCameraCopy.x, verticeInCameraCopy.y, verticeInCameraCopy.z, 1]; - var column2 = inverseMatrix.multiplyColumn(column); - var verticeInWorld = new Vertice(column2[0], column2[1], column2[2]); - return verticeInWorld; - } - - //向量变换: Camera->World - convertVectorFromCameraToWorld(vectorInCamera: Vector, /*optional*/ viewMatrix?: Matrix): Vector { - if (!(vectorInCamera instanceof Vector)) { - throw "invalid vectorInCamera: not Vector"; - } - if (!(viewMatrix instanceof Matrix)) { - viewMatrix = this.getViewMatrix(); - } - var vectorInCameraCopy = vectorInCamera.clone(); - var verticeInCamera = vectorInCameraCopy.getVertice(); - var verticeInWorld = this.convertVerticeFromCameraToWorld(verticeInCamera, viewMatrix); - var originInWorld = this.getPosition(); - var vectorInWorld = Vector.verticeMinusVertice(verticeInWorld, originInWorld); - vectorInWorld.normalize(); - return vectorInWorld; - } - - //根据canvasX和canvasY获取拾取向量 - getPickDirectionByCanvas(canvasX: number, canvasY: number): Vector { - var ndcXY = MathUtils.convertPointFromCanvasToNDC(canvasX, canvasY); - var pickDirection = this.getPickDirectionByNDC(ndcXY[0], ndcXY[1]); - return pickDirection; - } - - //获取当前视线与地球的交点 - getDirectionIntersectPointWithEarth(): Vertice[] { - var dir = this.getLightDirection(); - var p = this.getPosition(); - var line = new Line(p, dir); - var result = this.getPickCartesianCoordInEarthByLine(line); - return result; - } - - //根据ndcX和ndcY获取拾取向量 - getPickDirectionByNDC(ndcX: number, ndcY: number): Vector { - var verticeInNDC = new Vertice(ndcX, ndcY, 0.499); - var verticeInWorld = this.convertVerticeFromNdcToWorld(verticeInNDC); - var cameraPositon = this.getPosition(); //摄像机的世界坐标 - var pickDirection = Vector.verticeMinusVertice(verticeInWorld, cameraPositon); - pickDirection.normalize(); - return pickDirection; - } - - //获取直线与地球的交点,该方法与World.Math.getLineIntersectPointWithEarth功能基本一样,只不过该方法对相交点进行了远近排序 - getPickCartesianCoordInEarthByLine(line: Line): Vertice[] { - var result: Vertice[] = []; - //pickVertice是笛卡尔空间直角坐标系中的坐标 - var pickVertices = MathUtils.getLineIntersectPointWithEarth(line); - if (pickVertices.length === 0) { - //没有交点 - result = []; - } else if (pickVertices.length == 1) { - //一个交点 - result = pickVertices; - } else if (pickVertices.length == 2) { - //两个交点 - var pickVerticeA = pickVertices[0]; - var pickVerticeB = pickVertices[1]; - var cameraVertice = this.getPosition(); - var lengthA = MathUtils.getLengthFromVerticeToVertice(cameraVertice, pickVerticeA); - var lengthB = MathUtils.getLengthFromVerticeToVertice(cameraVertice, pickVerticeB); - //将距离人眼更近的那个点放到前面 - result = lengthA <= lengthB ? [pickVerticeA, pickVerticeB] : [pickVerticeB, pickVerticeA]; - } - return result; - } - - //计算拾取射线与地球的交点,以笛卡尔空间直角坐标系坐标数组的形式返回 - getPickCartesianCoordInEarthByCanvas(canvasX: number, canvasY: number): Vertice[] { - var pickDirection = this.getPickDirectionByCanvas(canvasX, canvasY); - var p = this.getPosition(); - var line = new Line(p, pickDirection); - var result = this.getPickCartesianCoordInEarthByLine(line); - return result; - } - - getPickCartesianCoordInEarthByNDC(ndcX: number, ndcY: number): Vertice[] { - var pickDirection = this.getPickDirectionByNDC(ndcX, ndcY); - var p = this.getPosition(); - var line = new Line(p, pickDirection); - var result = this.getPickCartesianCoordInEarthByLine(line); - return result; - } - - //得到摄像机的XOZ平面的方程 - getPlanXOZ(): Plan { - var position = this.getPosition(); - var direction = this.getLightDirection(); - var plan = MathUtils.getCrossPlaneByLine(position, direction); - return plan; - } - - isAnimating(): boolean{ - return this.animating; - } - - animateToLevel(level: number): void { - var newMat = this._animateToLevel(level); - this._animateToMatrix(newMat, () => { - (Kernel.globe as Globe).CURRENT_LEVEL = level; - }); - } - - private _animateToMatrix(newMat: Matrix, cb: ()=>void){ - if(this.isAnimating()){ - return; - } - this.animating = true; - var oldPosition = this.getPosition(); - var newPosition = newMat.getPosition(); - var span = 1000; - var singleSpan = 1000 / 60; - var count = Math.floor(span / singleSpan); - var deltaX = (newPosition.x - oldPosition.x) / count; - var deltaY = (newPosition.y - oldPosition.y) / count; - var deltaZ = (newPosition.z - oldPosition.z) / count; - var start:number = -1; - var callback = (timestap: number) => { - if(start < 0){ - start = timestap; - } - var a = timestap - start; - if(a >= span){ - this.matrix = newMat; - this.animating = false; - cb(); - }else{ - var p = this.getPosition(); - this.setPosition(p.x + deltaX, p.y + deltaY, p.z + deltaZ); - requestAnimationFrame(callback); - } - }; - requestAnimationFrame(callback); - } - - private _animateToLevel(level: number): Matrix{ - if (!(Utils.isNonNegativeInteger(level))) { - throw "invalid level:" + level; - } - var camera = this._clone(); - //don't call setLevel method because it will update CURRENT_LEVEL - camera._setLevel(level); - return camera.matrix; - } - - private _clone(): PerspectiveCamera{ - var camera: PerspectiveCamera = new PerspectiveCamera(); - camera.pitch = this.pitch; - camera.matrix = this.matrix.clone(); - camera.projMatrix = this.projMatrix.clone(); - return camera; - } - - setLevel(level: number): void{ - this._setLevel(level); - //don't update CURRENT_LEVEL in _setLevel method because it will affect animateToLevel method - Kernel.globe.CURRENT_LEVEL = level; - } - - //设置观察到的层级 - private _setLevel(level: number): void { - if (!(Utils.isNonNegativeInteger(level))) { - throw "invalid level:" + level; - } - var pOld = this.getPosition(); - if (pOld.x === 0 && pOld.y === 0 && pOld.z === 0) { - //初始设置camera - var length = MathUtils.getLengthFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 - var origin = new Vertice(0, 0, 0); - var vector = this.getLightDirection().getOpposite(); - vector.setLength(length); - var newPosition = vector.getVertice(); - this.look(newPosition, origin); - } else { - var length2SurfaceNow = MathUtils.getLengthFromCamera2EarthSurface(Kernel.globe.CURRENT_LEVEL); - var length2Surface = MathUtils.getLengthFromCamera2EarthSurface(level); - var deltaLength = length2SurfaceNow - length2Surface; - var dir = this.getLightDirection(); - dir.setLength(deltaLength); - var pNew = Vector.verticePlusVector(pOld, dir); - this.setPosition(pNew.x, pNew.y, pNew.z); - } - } - - //判断世界坐标系中的点是否在Canvas中可见 - //options:projView、verticeInNDC - isWorldVerticeVisibleInCanvas(verticeInWorld: Vertice, options?: any): boolean { - if (!(verticeInWorld instanceof Vertice)) { - throw "invalid verticeInWorld: not Vertice"; - } - options = options || {}; - var threshold = typeof options.threshold == "number" ? Math.abs(options.threshold) : 1; - var cameraP = this.getPosition(); - var dir = Vector.verticeMinusVertice(verticeInWorld, cameraP); - var line = new Line(cameraP, dir); - var pickResult = this.getPickCartesianCoordInEarthByLine(line); - if (pickResult.length > 0) { - var pickVertice = pickResult[0]; - var length2Vertice = MathUtils.getLengthFromVerticeToVertice(cameraP, verticeInWorld); - var length2Pick = MathUtils.getLengthFromVerticeToVertice(cameraP, pickVertice); - if (length2Vertice < length2Pick + 5) { - if (!(options.verticeInNDC instanceof Vertice)) { - if (!(options.projView instanceof Matrix)) { - options.projView = this.getProjViewMatrix(); - } - options.verticeInNDC = this.convertVerticeFromWorldToNDC(verticeInWorld, options.projView); - } - var result = options.verticeInNDC.x >= -1 && options.verticeInNDC.x <= 1 && options.verticeInNDC.y >= -threshold && options.verticeInNDC.y <= 1; - return result; - } - } - return false; - } - - //判断地球表面的某个经纬度在Canvas中是否应该可见 - //options:projView、verticeInNDC - isGeoVisibleInCanvas(lon: number, lat: number, options?: any): boolean { - var verticeInWorld = MathUtils.geographicToCartesianCoord(lon, lat); - var result = this.isWorldVerticeVisibleInCanvas(verticeInWorld, options); - return result; - } - - /** - * 算法,一个切片需要渲染需要满足如下三个条件: - * 1.至少要有一个点在Canvas中可见 - * 2.NDC面积足够大 - * 3.形成的NDC四边形是顺时针方向 - */ - //获取level层级下的可见切片 - //options:projView - getVisibleTilesByLevel(level: number, options?: any): TileGrid[] { - if (!(level >= 0)) { - throw "invalid level"; - } - var result: TileGrid[] = []; - options = options || {}; - if (!(options.projView instanceof Matrix)) { - options.projView = this.getProjViewMatrix(); - } - //向左、向右、向上、向下最大的循环次数 - var LOOP_LIMIT = Math.min(10, Math.pow(2, level) - 1); - - var mathOptions = { - maxSize: Math.pow(2, level) - }; - - function checkVisible(visibleInfo: any) { - if (visibleInfo.area >= 5000 && visibleInfo.clockwise) { - if (visibleInfo.visibleCount >= 1) { - return true; - } - } - return false; - } - - function handleRow(centerRow: number, centerColumn: number) { - var result: TileGrid[] = []; - var grid = new TileGrid(level, centerRow, centerColumn); // {level:level,row:centerRow,column:centerColumn}; - var visibleInfo = this.getTileVisibleInfo(grid.level, grid.row, grid.column, options); - var isRowCenterVisible = checkVisible(visibleInfo); - if (isRowCenterVisible) { - (grid as any).visibleInfo = visibleInfo; - result.push(grid); - - //向左遍历至不可见 - var leftLoopTime = 0; //向左循环的次数 - var leftColumn = centerColumn; - var visible: boolean; - while (leftLoopTime < LOOP_LIMIT) { - leftLoopTime++; - grid = TileGrid.getTileGridByBrother(level, centerRow, leftColumn, MathUtils.LEFT, mathOptions); - leftColumn = grid.column; - visibleInfo = this.getTileVisibleInfo(grid.level, grid.row, grid.column, options); - visible = checkVisible(visibleInfo); - if (visible) { - (grid).visibleInfo = visibleInfo; - result.push(grid); - } else { - break; - } - } - - //向右遍历至不可见 - var rightLoopTime = 0; //向右循环的次数 - var rightColumn = centerColumn; - while (rightLoopTime < LOOP_LIMIT) { - rightLoopTime++; - grid = TileGrid.getTileGridByBrother(level, centerRow, rightColumn, MathUtils.RIGHT, mathOptions); - rightColumn = grid.column; - visibleInfo = this.getTileVisibleInfo(grid.level, grid.row, grid.column, options); - visible = checkVisible(visibleInfo); - if (visible) { - (grid).visibleInfo = visibleInfo; - result.push(grid); - } else { - break; - } - } - } - return result; - } - - var verticalCenterInfo = this._getVerticalVisibleCenterInfo(options); - var centerGrid = TileGrid.getTileGridByGeo(verticalCenterInfo.lon, verticalCenterInfo.lat, level); - var handleRowThis = handleRow.bind(this); - - var rowResult = handleRowThis(centerGrid.row, centerGrid.column); - result = result.concat(rowResult); - var grid: TileGrid; - - //循环向下处理至不可见 - var bottomLoopTime = 0; //向下循环的次数 - var bottomRow = centerGrid.row; - while (bottomLoopTime < LOOP_LIMIT) { - bottomLoopTime++; - grid = TileGrid.getTileGridByBrother(level, bottomRow, centerGrid.column, MathUtils.BOTTOM, mathOptions); - bottomRow = grid.row; - rowResult = handleRowThis(grid.row, grid.column); - if (rowResult.length > 0) { - result = result.concat(rowResult); - } else { - //已经向下循环到不可见,停止向下循环 - break; - } - } - - //循环向上处理至不可见 - var topLoopTime = 0; //向上循环的次数 - var topRow = centerGrid.row; - while (topLoopTime < LOOP_LIMIT) { - topLoopTime++; - grid = TileGrid.getTileGridByBrother(level, topRow, centerGrid.column, MathUtils.TOP, mathOptions); - topRow = grid.row; - rowResult = handleRowThis(grid.row, grid.column); - if (rowResult.length > 0) { - result = result.concat(rowResult); - } else { - //已经向上循环到不可见,停止向上循环 - break; - } - } - - return result; - } - - //options:projView - getTileVisibleInfo(level: number, row: number, column: number, options?: any): any { - if (!(level >= 0)) { - throw "invalid level"; - } - if (!(row >= 0)) { - throw "invalid row"; - } - if (!(column >= 0)) { - throw "invalid column"; - } - options = options || {}; - var threshold = typeof options.threshold == "number" ? Math.abs(options.threshold) : 1; - var result: any = { - lb: { - lon: null, - lat: null, - verticeInWorld: null, - verticeInNDC: null, - visible: false - }, - lt: { - lon: null, - lat: null, - verticeInWorld: null, - verticeInNDC: null, - visible: false - }, - rt: { - lon: null, - lat: null, - verticeInWorld: null, - verticeInNDC: null, - visible: false - }, - rb: { - lon: null, - lat: null, - verticeInWorld: null, - verticeInNDC: null, - visible: false - }, - Egeo: null, - visibleCount: 0, - clockwise: false, - width: null, - height: null, - area: null - }; - if (!(options.projView instanceof Matrix)) { - options.projView = this.getProjViewMatrix(); - } - result.Egeo = MathUtils.getTileGeographicEnvelopByGrid(level, row, column); - var tileMinLon = result.Egeo.minLon; - var tileMaxLon = result.Egeo.maxLon; - var tileMinLat = result.Egeo.minLat; - var tileMaxLat = result.Egeo.maxLat; - - //左下角 - result.lb.lon = tileMinLon; - result.lb.lat = tileMinLat; - result.lb.verticeInWorld = MathUtils.geographicToCartesianCoord(result.lb.lon, result.lb.lat); - result.lb.verticeInNDC = this.convertVerticeFromWorldToNDC(result.lb.verticeInWorld, options.projView); - result.lb.visible = this.isWorldVerticeVisibleInCanvas(result.lb.verticeInWorld, { - verticeInNDC: result.lb.verticeInNDC, - projView: options.projView, - threshold: threshold - }); - if (result.lb.visible) { - result.visibleCount++; - } - - //左上角 - result.lt.lon = tileMinLon; - result.lt.lat = tileMaxLat; - result.lt.verticeInWorld = MathUtils.geographicToCartesianCoord(result.lt.lon, result.lt.lat); - result.lt.verticeInNDC = this.convertVerticeFromWorldToNDC(result.lt.verticeInWorld, options.projView); - result.lt.visible = this.isWorldVerticeVisibleInCanvas(result.lt.verticeInWorld, { - verticeInNDC: result.lt.verticeInNDC, - projView: options.projView, - threshold: threshold - }); - if (result.lt.visible) { - result.visibleCount++; - } - - //右上角 - result.rt.lon = tileMaxLon; - result.rt.lat = tileMaxLat; - result.rt.verticeInWorld = MathUtils.geographicToCartesianCoord(result.rt.lon, result.rt.lat); - result.rt.verticeInNDC = this.convertVerticeFromWorldToNDC(result.rt.verticeInWorld, options.projView); - result.rt.visible = this.isWorldVerticeVisibleInCanvas(result.rt.verticeInWorld, { - verticeInNDC: result.rt.verticeInNDC, - projView: options.projView, - threshold: threshold - }); - if (result.rt.visible) { - result.visibleCount++; - } - - //右下角 - result.rb.lon = tileMaxLon; - result.rb.lat = tileMinLat; - result.rb.verticeInWorld = MathUtils.geographicToCartesianCoord(result.rb.lon, result.rb.lat); - result.rb.verticeInNDC = this.convertVerticeFromWorldToNDC(result.rb.verticeInWorld, options.projView); - result.rb.visible = this.isWorldVerticeVisibleInCanvas(result.rb.verticeInWorld, { - verticeInNDC: result.rb.verticeInNDC, - projView: options.projView, - threshold: threshold - }); - if (result.rb.visible) { - result.visibleCount++; - } - - var ndcs: Vertice[] = [result.lb.verticeInNDC, result.lt.verticeInNDC, result.rt.verticeInNDC, result.rb.verticeInNDC]; - //计算方向 - var vector03 = Vector.verticeMinusVertice(ndcs[3], ndcs[0]); - vector03.z = 0; - var vector01 = Vector.verticeMinusVertice(ndcs[1], ndcs[0]); - vector01.z = 0; - var cross = vector03.cross(vector01); - result.clockwise = cross.z > 0; - //计算面积 - 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; - 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; - result.width = Math.floor((topWidth + bottomWidth) / 2); - 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; - 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; - result.height = Math.floor((leftHeight + rightHeight) / 2); - result.area = result.width * result.height; - - return result; - } - - //地球一直是关于纵轴中心对称的,获取垂直方向上中心点信息 - private _getVerticalVisibleCenterInfo(options?: any): any { - options = options || {}; - if (!options.projView) { - options.projView = this.getProjViewMatrix(); - } - var result = { - ndcY: null, - pIntersect: null, - lon: null, - lat: null - }; - var pickResults: Vertice[]; - if (this.pitch == 90) { - result.ndcY = 0; - } else { - var count = 10; - var delta = 2.0 / count; - var topNdcY = 1; - var bottomNdcY = -1; - var ndcY: number; - //从上往下找topNdcY - for (ndcY = 1.0; ndcY >= -1.0; ndcY -= delta) { - pickResults = this.getPickCartesianCoordInEarthByNDC(0, ndcY); - if (pickResults.length > 0) { - topNdcY = ndcY; - break; - } - } - - //从下往上找 - for (ndcY = -1.0; ndcY <= 1.0; ndcY += delta) { - pickResults = this.getPickCartesianCoordInEarthByNDC(0, ndcY); - if (pickResults.length > 0) { - bottomNdcY = ndcY; - break; - } - } - result.ndcY = (topNdcY + bottomNdcY) / 2; - } - pickResults = this.getPickCartesianCoordInEarthByNDC(0, result.ndcY); - result.pIntersect = pickResults[0]; - var lonlat = MathUtils.cartesianCoordToGeographic(result.pIntersect); - result.lon = lonlat[0]; - result.lat = lonlat[1]; - return result; - } -} - -export = PerspectiveCamera; \ No newline at end of file diff --git a/src/world/Program.ts b/src/world/Program.ts new file mode 100644 index 0000000..ae2ad6a --- /dev/null +++ b/src/world/Program.ts @@ -0,0 +1,186 @@ +/// + +import Kernel = require("./Kernel"); +import Graphic = require("./graphics/Graphic"); + +class Program{ + ready: boolean = false; + activeInfosObject: any; + program: WebGLProgram; + private static currentProgram: Program; + private static readonly programs: Program[] = []; + + constructor(public type:string, public vs:string, public fs:string){ + //{name,type,size,loc,isAttribute, isEnabled} the default value of isEnabled is undefined + //Note: if attribute, loc is number; if uniform, loc is WebGLUniformLocation + this.activeInfosObject = {}; + this._init(); + } + + static getProgram(graphic: Graphic){ + var program:Program = null; + + var programType = graphic.getProgramType(); + + Program.programs.some(function(item){ + if(item.type === graphic.getProgramType()){ + program = item; + return true; + }else{ + return false; + } + }); + + if(!program){ + program = graphic.createProgram(); + Program.programs.push(program); + } + + return program; + } + + use(){ + if(this.ready && Program.currentProgram !== this){ + Kernel.gl.useProgram(this.program); + Program.currentProgram = this; + } + } + + updateActiveAttribInfos(){ + var count = Kernel.gl.getProgramParameter(this.program, Kernel.gl.ACTIVE_ATTRIBUTES); + + for(var i = 0, activeInfo: any; i < count; i++){ + activeInfo = Kernel.gl.getActiveAttrib(this.program, i); + activeInfo.loc = Kernel.gl.getAttribLocation(this.program, activeInfo.name); + activeInfo.isAttribute = true; + this.activeInfosObject[activeInfo.name] = activeInfo; + } + } + + updateActiveUniformInfos(){ + var count = Kernel.gl.getProgramParameter(this.program, Kernel.gl.ACTIVE_UNIFORMS); + + for(var i = 0, activeInfo: any; i < count; i++){ + activeInfo = Kernel.gl.getActiveUniform(this.program, i); + activeInfo.loc = Kernel.gl.getUniformLocation(this.program, activeInfo.name); + activeInfo.isAttribute = false; + this.activeInfosObject[activeInfo.name] = activeInfo; + } + } + + getLocation(name: string){ + //loc = gl.getAttribLocation(this.program, name); + //loc = gl.getUniformLocation(this.program, name); + var loc = -1; + var activeInfo = this.activeInfosObject[name]; + if(activeInfo){ + loc = activeInfo.loc; + } + return loc; + } + + getAttribLocation(name: string){ + var loc = -1; + var activeInfo = this.activeInfosObject[name]; + if(activeInfo && activeInfo.isAttribute){ + loc = activeInfo.loc; + } + return loc; + } + + //return WebGLUniformLocation, not a number + getUniformLocation(name: string){ + var loc: WebGLUniformLocation; + var activeInfo = this.activeInfosObject[name]; + if(activeInfo && !activeInfo.isAttribute){ + loc = activeInfo.loc; + } + return loc; + } + + //VertexAttributeState + getVertexAttrib(){ + /*VERTEX_ATTRIB_ARRAY_ENABLED + VERTEX_ATTRIB_ARRAY_SIZE + VERTEX_ATTRIB_ARRAY_STRIDE + VERTEX_ATTRIB_ARRAY_TYPE + VERTEX_ATTRIB_ARRAY_NORMALIZED + VERTEX_ATTRIB_ARRAY_BUFFER_BINDING + CURRENT_VERTEX_ATTRIB*/ + } + + //Return the uniform value at the passed location in the passed program. + //The type returned is dependent on the uniform type. + getUniform(name:string){ + var result: any; + var loc = this.getUniformLocation(name); + if(loc){ + result = Kernel.gl.getUniform(this.program, loc); + } + return result; + } + + enableVertexAttribArray(name:string){ + var activeInfo = this.activeInfosObject[name]; + if(activeInfo && activeInfo.isAttribute && activeInfo.isEnabled !== true){ + var loc = activeInfo.loc; + Kernel.gl.enableVertexAttribArray(loc); + activeInfo.isEnabled = true; + } + } + + disableVertexAttribArray(name:string){ + var activeInfo = this.activeInfosObject[name]; + if(activeInfo && activeInfo.isAttribute && activeInfo.isEnabled !== false){ + var loc = activeInfo.loc; + Kernel.gl.disableVertexAttribArray(loc); + activeInfo.isEnabled = false; + } + } + + _init(){ + var vs = this._getShader(Kernel.gl.VERTEX_SHADER, this.vs); + if(!vs){ + return; + } + + var fs = this._getShader(Kernel.gl.FRAGMENT_SHADER, this.fs); + if(!fs){ + return; + } + + this.program = Kernel.gl.createProgram(); + Kernel.gl.attachShader(this.program, vs); + Kernel.gl.attachShader(this.program, fs); + Kernel.gl.linkProgram(this.program); + + if (!Kernel.gl.getProgramParameter(this.program, Kernel.gl.LINK_STATUS)) { + console.error("Could not link program!"); + Kernel.gl.deleteProgram(this.program); + Kernel.gl.deleteShader(vs); + Kernel.gl.deleteShader(fs); + this.program = null; + return; + } + + this.updateActiveAttribInfos(); + this.updateActiveUniformInfos(); + this.ready = true; + } + + _getShader(shaderType:number, shaderText:string){ + var shader = Kernel.gl.createShader(shaderType); + Kernel.gl.shaderSource(shader, shaderText); + Kernel.gl.compileShader(shader); + + if(!Kernel.gl.getShaderParameter(shader, Kernel.gl.COMPILE_STATUS)){ + console.error("create shader failed", Kernel.gl.getShaderInfoLog(shader)); + Kernel.gl.deleteShader(shader); + return null; + } + + return shader; + } +} + +export = Program; \ No newline at end of file diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 317b9fb..8b9e67b 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -2,24 +2,15 @@ import Kernel = require("./Kernel"); import EventUtils = require("./Event"); import Scene = require("./Scene"); -import PerspectiveCamera = require("./PerspectiveCamera"); -import {WebGLRenderingContextExtension, WebGLProgramExtension} from "./Definitions"; +import Camera from "./Camera"; +import { WebGLRenderingContextExtension, WebGLProgramExtension } from "./Definitions"; class Renderer { scene: Scene = null; - camera: PerspectiveCamera = null; - bAutoRefresh: boolean = false; + camera: Camera = null; + autoRefresh: boolean = false; - constructor(canvas: HTMLCanvasElement, vertexShaderText: string, fragmentShaderText: string) { - if (!(vertexShaderText !== "")) { - throw "invalid vertexShaderText"; - } - if (!(fragmentShaderText !== "")) { - throw "invalid fragmentShaderText"; - } - //之所以在此处设置Kernel.renderer是因为要在tick函数中使用 - Kernel.renderer = this; - + constructor(canvas: HTMLCanvasElement) { EventUtils.bindEvents(canvas); var gl: WebGLRenderingContextExtension; @@ -33,6 +24,7 @@ class Renderer { }) as WebGLRenderingContextExtension; if (gl) { Kernel.gl = gl; + (window).gl = gl; Kernel.canvas = canvas; break; } @@ -40,72 +32,6 @@ class Renderer { } catch (e) { } } - function getShader(gl: WebGLRenderingContextExtension, shaderType: string, shaderText: string) { - if (!shaderText) { - return null; - } - - var shader: WebGLShader = null; - if (shaderType == "VERTEX_SHADER") { - shader = gl.createShader(gl.VERTEX_SHADER); - } else if (shaderType == "FRAGMENT_SHADER") { - shader = gl.createShader(gl.FRAGMENT_SHADER); - } else { - return null; - } - - gl.shaderSource(shader, shaderText); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - alert(gl.getShaderInfoLog(shader)); - console.error(gl.getShaderInfoLog(shader)); - gl.deleteShader(shader); - return null; - } - - return shader; - } - - function initShaders(vertexShaderText: string, fragmentShaderText: string) { - var vertexShader = getShader(Kernel.gl, "VERTEX_SHADER", vertexShaderText); - var fragmentShader = getShader(Kernel.gl, "FRAGMENT_SHADER", fragmentShaderText); - - var shaderProgram = gl.createProgram(); - gl.attachShader(shaderProgram, vertexShader); - gl.attachShader(shaderProgram, fragmentShader); - gl.linkProgram(shaderProgram); - - if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { - console.error("Could not link program!"); - gl.deleteProgram(shaderProgram); - gl.deleteShader(vertexShader); - gl.deleteShader(fragmentShader); - return; - } - - gl.useProgram(shaderProgram); - gl.shaderProgram = shaderProgram as WebGLProgramExtension; - gl.shaderProgram.aVertexPosition = gl.getAttribLocation(gl.shaderProgram, "aVertexPosition"); - gl.shaderProgram.aTextureCoord = gl.getAttribLocation(gl.shaderProgram, "aTextureCoord"); - gl.shaderProgram.uMVMatrix = gl.getUniformLocation(gl.shaderProgram, "uMVMatrix"); - gl.shaderProgram.uPMatrix = gl.getUniformLocation(gl.shaderProgram, "uPMatrix"); - gl.shaderProgram.uSampler = gl.getUniformLocation(gl.shaderProgram, "uSampler"); //纹理采样器 - gl.shaderProgram.uOffScreen = gl.getUniformLocation(gl.shaderProgram, "uOffScreen"); //是否离屏渲染 - - //设置默认非离屏渲染 - gl.uniform1i(gl.shaderProgram.uOffScreen, 1); - - //设置默认值 - var squareArray = [1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ]; - var squareMatrix = new Float32Array(squareArray); //ArrayBuffer - gl.uniformMatrix4fv(gl.shaderProgram.uMVMatrix, false, squareMatrix); - } - initWebGL(canvas); if (!gl) { @@ -114,52 +40,54 @@ class Renderer { return; } - initShaders(vertexShaderText, fragmentShaderText); - + Kernel.gl.clear(Kernel.gl.COLOR_BUFFER_BIT | Kernel.gl.DEPTH_BUFFER_BIT); gl.clearColor(255, 255, 255, 1.0); - //gl.enable(gl.DEPTH_TEST); - gl.disable(gl.DEPTH_TEST); //此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 + + gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); + gl.depthMask(true);//允许写入深度 gl.enable(gl.CULL_FACE); //一定要启用裁剪,否则显示不出立体感 - gl.frontFace(gl.CCW); + gl.frontFace(gl.CCW);//指定逆时针方向为正面 gl.cullFace(gl.BACK); //裁剪掉背面 //gl.enable(gl.TEXTURE_2D);//WebGL: INVALID_ENUM: enable: invalid capability } - render(scene: Scene, camera: PerspectiveCamera) { - Kernel.gl.viewport(0, 0, Kernel.canvas.width, Kernel.canvas.height); - Kernel.gl.clear(Kernel.gl.COLOR_BUFFER_BIT | Kernel.gl.DEPTH_BUFFER_BIT); - camera.viewMatrix = null; - camera.viewMatrix = camera.getViewMatrix(); + render(scene: Scene, camera: Camera) { + var gl = Kernel.gl; + gl.viewport(0, 0, Kernel.canvas.width, Kernel.canvas.height); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.clearColor(0, 0, 0, 1); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + gl.depthMask(true); + camera.update(); scene.draw(camera); } - bindScene(scene: Scene) { + setScene(scene: Scene) { this.scene = scene; } - bindCamera(camera: PerspectiveCamera) { + setCamera(camera: Camera) { this.camera = camera; } - tick() { - if (Kernel.renderer instanceof Renderer) { - if (Kernel.renderer.scene && Kernel.renderer.camera) { - Kernel.renderer.render(Kernel.renderer.scene, Kernel.renderer.camera); - } + private _tick() { + if (this.scene && this.camera) { + this.render(this.scene, this.camera); + } - if (Kernel.renderer.bAutoRefresh) { - window.requestAnimationFrame(Kernel.renderer.tick); - } + if (this.autoRefresh) { + window.requestAnimationFrame(this._tick.bind(this)); } } - setIfAutoRefresh(bAuto: boolean) { - this.bAutoRefresh = bAuto; - if (this.bAutoRefresh) { - this.tick(); + setIfAutoRefresh(auto: boolean) { + this.autoRefresh = auto; + if (this.autoRefresh) { + this._tick(); } } } diff --git a/src/world/Scene.ts b/src/world/Scene.ts index d2c1053..f7be677 100644 --- a/src/world/Scene.ts +++ b/src/world/Scene.ts @@ -1,8 +1,8 @@ /// -import Object3DComponents = require('./Object3DComponents'); -import TiledLayer = require("./TiledLayer"); +import GraphicGroup = require('./GraphicGroup'); +import TiledLayer = require("./layers/TiledLayer"); -class Scene extends Object3DComponents{ +class Scene extends GraphicGroup{ tiledLayer: TiledLayer; } diff --git a/src/world/ShaderContent.ts b/src/world/ShaderContent.ts deleted file mode 100644 index 60eb293..0000000 --- a/src/world/ShaderContent.ts +++ /dev/null @@ -1,48 +0,0 @@ -/// - -const vsShader = -` -attribute vec3 aVertexPosition; -attribute vec2 aTextureCoord; -varying vec2 vTextureCoord; - -uniform mat4 uMVMatrix; -uniform mat4 uPMatrix; - -void main() -{ - gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition,1.0); - vTextureCoord = aTextureCoord; -} -`; - -const fsShader = -` -#ifdef GL_ES -precision highp float; -#endif - -uniform bool uUseTexture; -uniform float uShininess; -uniform vec3 uLightDirection; - -uniform vec4 uLightAmbient; -uniform vec4 uLightDiffuse; -uniform vec4 uLightSpecular; - -varying vec2 vTextureCoord; -uniform sampler2D uSampler; - -void main() -{ - gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); -} -`; - - -export = { - SIMPLE_SHADER: { - VS_CONTENT: vsShader, - FS_CONTENT: fsShader - } -}; \ No newline at end of file diff --git a/src/world/SosoTiledLayer.ts b/src/world/SosoTiledLayer.ts deleted file mode 100644 index 952fbf1..0000000 --- a/src/world/SosoTiledLayer.ts +++ /dev/null @@ -1,21 +0,0 @@ -/// -import TiledLayer = require('./TiledLayer'); - -class SosoTiledLayer extends TiledLayer { - getImageUrl(level: number, row: number, column: number): string { - var url = ""; - var tileCount = Math.pow(2, level); - var a = column; - var b = tileCount - row - 1; - var A = Math.floor(a / 16); - var B = Math.floor(b / 16); - var sum = level + row + column; - var serverIdx = sum % 4; //0、1、2、3 - var sateUrl = "//p" + serverIdx + ".map.soso.com/sateTiles/" + level + "/" + A + "/" + B + "/" + a + "_" + b + ".jpg"; - //var maptileUrl = "http://p"+serverIdx+".map.soso.com/maptilesv2/"+level+"/"+A+"/"+B+"/"+a+"_"+b+".png"; - url = sateUrl; - return url; - } -} - -export = SosoTiledLayer; \ No newline at end of file diff --git a/src/world/SubTiledLayer.ts b/src/world/SubTiledLayer.ts deleted file mode 100644 index c9ca3fa..0000000 --- a/src/world/SubTiledLayer.ts +++ /dev/null @@ -1,187 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Utils = require('./Utils'); -import MathUtils = require('./Math'); -import TileGrid = require('./TileGrid'); -import Object3DComponents = require('./Object3DComponents'); -import Tile = require('./Tile'); -import Elevation = require('./Elevation'); - -class SubTiledLayer extends Object3DComponents { - level: number = -1; - //该级要请求的高程数据的层级,7[8,9,10];10[11,12,13];13[14,15,16];16[17,18,19] - elevationLevel = -1; - tiledLayer: any = null; - - constructor(args: any) { - super(); - this.level = args.level; - this.elevationLevel = Elevation.getAncestorElevationLevel(this.level); - } - - //重写draw方法 - draw(camera: any) { - if (this.level >= Kernel.TERRAIN_LEVEL && Kernel.globe && Kernel.globe.camera.pitch <= Kernel.TERRAIN_PITCH) { - Kernel.gl.clear(Kernel.gl.DEPTH_BUFFER_BIT); - Kernel.gl.clearDepth(1); - Kernel.gl.enable(Kernel.gl.DEPTH_TEST); - } else { - Kernel.gl.disable(Kernel.gl.DEPTH_TEST); - } - super.draw(camera); - } - - //重写Object3DComponents的add方法 - add(tile: Tile) { - if (tile.level === this.level) { - super.add(tile); - tile.subTiledLayer = this; - } - } - - //调用其父的getImageUrl - // getImageUrl(level: number, row: number, column: number) { - // var url = ""; - // if (this.tiledLayer) { - // url = this.tiledLayer.getImageUrl(level, row, column); - // } - // return url; - // } - - //重写Object3DComponents的destroy方法 - destroy() { - super.destroy(); - this.tiledLayer = null; - } - - //根据level、row、column查找tile,可以供调试用 - findTile(level: number, row: number, column: number) { - var length = this.children.length; - for (var i = 0; i < length; i++) { - var tile = this.children[i]; - if (tile.level === level && tile.row === row && tile.column === column) { - return tile; - } - } - return null; - } - - //根据传入的tiles信息进行更新其children - updateTiles(visibleTileGrids: TileGrid[], bAddNew: boolean) { //camera,options - //var visibleTileGrids = camera.getVisibleTilesByLevel(this.level,options); - //检查visibleTileGrids中是否存在指定的切片信息 - function checkTileExist(tileArray: TileGrid[], lev: number, row: number, col: number): any { - var result = { - isExist: false, - index: -1 - }; - for (var m = 0; m < tileArray.length; m++) { - var tileInfo = tileArray[m]; - if (tileInfo.level === lev && tileInfo.row === row && tileInfo.column === col) { - result.isExist = true; - result.index = m; - return result; - } - } - return result; - } - - //记录应该删除的切片 - var tilesNeedDelete: Tile[] = []; - var i:number, tile:Tile; - for (i = 0; i < this.children.length; i++) { - tile = this.children[i]; - var checkResult = checkTileExist(visibleTileGrids, tile.level, tile.row, tile.column); - var isExist = checkResult.isExist; - if (isExist) { - visibleTileGrids.splice(checkResult.index, 1); //已处理 - } else { - //暂时不删除,先添加要删除的标记,循环删除容易出错 - tilesNeedDelete.push(tile); - } - } - - //集中进行删除 - while (tilesNeedDelete.length > 0) { - var b = this.remove(tilesNeedDelete[0]); - tilesNeedDelete.splice(0, 1); - if (!b) { - console.debug("LINE:2191,subTiledLayer.remove(tilesNeedDelete[0])失败"); - } - } - - if (bAddNew) { - //添加新增的切片 - for (i = 0; i < visibleTileGrids.length; i++) { - var tileGridInfo = visibleTileGrids[i]; - var args = { - level: tileGridInfo.level, - row: tileGridInfo.row, - column: tileGridInfo.column, - url: "" - }; - args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); - tile = new Tile(args); - this.add(tile); - } - } - } - - //如果bForce为true,则表示强制显示为三维,不考虑level - checkTerrain(bForce:boolean = false) { - var globe = Kernel.globe; - var show3d = bForce === true ? true : this.level >= Kernel.TERRAIN_LEVEL; - if (show3d && globe && globe.camera && globe.camera.pitch < Kernel.TERRAIN_PITCH) { - var tiles = this.children; - for (var i = 0; i < tiles.length; i++) { - var tile = tiles[i]; - tile.checkTerrain(bForce); - } - } - } - - //根据当前子图层下的tiles获取其对应的祖先高程切片的TileGrid //getAncestorElevationTileGrids - //7 8 9 10; 10 11 12 13; 13 14 15 16; 16 17 18 19; - requestElevations() { - var result:any[] = []; - if (this.level > Kernel.ELEVATION_LEVEL) { - var tiles = this.children; - var i:number, name:any; - for (i = 0; i < tiles.length; i++) { - var tile = tiles[i]; - var tileGrid = TileGrid.getTileGridAncestor(this.elevationLevel, tile.level, tile.row, tile.column); - name = tileGrid.level + "_" + tileGrid.row + "_" + tileGrid.column; - if (result.indexOf(name) < 0) { - result.push(name); - } - } - for (i = 0; i < result.length; i++) { - name = result[i]; - var a = name.split('_'); - var eleLevel = parseInt(a[0]); - var eleRow = parseInt(a[1]); - var eleColumn = parseInt(a[2]); - //只要elevations中有属性name,那么就表示该高程已经请求过或正在请求,这样就不要重新请求了 - //只有在完全没请求过的情况下去请求高程数据 - if (!Elevation.elevations.hasOwnProperty(name)) { - Elevation.requestElevationsByTileGrid(eleLevel, eleRow, eleColumn); - } - } - } - } - - checkIfLoaded() { - for (var i = 0; i < this.children.length; i++) { - var tile = this.children[i]; - if (tile) { - var isTileLoaded = tile.material.loaded; - if (!isTileLoaded) { - return false; - } - } - } - return true; - } -} - -export = SubTiledLayer; \ No newline at end of file diff --git a/src/world/TextureMaterial.ts b/src/world/TextureMaterial.ts deleted file mode 100644 index c571314..0000000 --- a/src/world/TextureMaterial.ts +++ /dev/null @@ -1,68 +0,0 @@ -/// -import Kernel = require('./Kernel'); - -class TextureMaterial { - texture: WebGLTexture = null; - image: HTMLImageElement = null; - loaded: boolean = false; - delete: boolean = false; - - constructor(args: any) { - this.texture = Kernel.gl.createTexture(); - this.image = null; - this.loaded = false; - this.delete = false; - if (args.image instanceof Image && args.image.width > 0 && args.image.height > 0) { - this.setImage(args.image); - } else if (typeof args.url == "string") { - this.setImageUrl(args.url); - } - } - - setImage(image: HTMLImageElement): void { - if (image.width > 0 && image.height > 0) { - this.image = image; - this.onLoad(); - } - }; - - setImageUrl(url: string): void { - this.image = new Image(); - this.image.crossOrigin = 'anonymous'; //很重要,因为图片是跨域获得的,所以一定要加上此句代码 - this.image.onload = this.onLoad.bind(this); - this.image.src = url; - }; - - //图片加载完成时触发 - onLoad(): void { - //要考虑纹理已经被移除掉了图片才进入onLoad这种情况 - if (this.delete) { - return; - } - - Kernel.gl.bindTexture(Kernel.gl.TEXTURE_2D, this.texture); - Kernel.gl.pixelStorei(Kernel.gl.UNPACK_FLIP_Y_WEBGL, +true); - - Kernel.gl.texImage2D(Kernel.gl.TEXTURE_2D, 0, Kernel.gl.RGBA, Kernel.gl.RGBA, Kernel.gl.UNSIGNED_BYTE, this.image); - //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - //使用MipMap - Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MIN_FILTER, Kernel.gl.LINEAR_MIPMAP_NEAREST); - Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MAG_FILTER, Kernel.gl.LINEAR); //LINEAR_MIPMAP_NEAREST LINEAR_MIPMAP_LINEAR - Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_WRAP_S, Kernel.gl.CLAMP_TO_EDGE); - Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_WRAP_T, Kernel.gl.CLAMP_TO_EDGE); - Kernel.gl.generateMipmap(Kernel.gl.TEXTURE_2D); - Kernel.gl.bindTexture(Kernel.gl.TEXTURE_2D, null); - this.loaded = true; - }; - - //释放显卡中的texture资源 - releaseTexture(): void { - if (Kernel.gl.isTexture(this.texture)) { - Kernel.gl.deleteTexture(this.texture); - this.delete = true; - } - }; -} - -export = TextureMaterial; \ No newline at end of file diff --git a/src/world/Tile.ts b/src/world/Tile.ts deleted file mode 100644 index 0284d03..0000000 --- a/src/world/Tile.ts +++ /dev/null @@ -1,230 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Object3D = require('./Object3D'); -import Enum = require('./Enum'); -import Elevation = require('./Elevation'); -import MathUtils = require('./Math'); -import TileMaterial = require('./TileMaterial'); - -class Tile extends Object3D { - level: number = 0; - row: number = 0; - column: number = 0; - url: string; - subTiledLayer: any; - //type如果是GLOBE_TILE,表示其buffer已经设置为一般形式 - //type如果是TERRAIN_TILE,表示其buffer已经设置为高程形式 - //type如果是UNKNOWN,表示buffer没设置 - type: number = Enum.UNKNOWN; - elevationLevel: number = 0;//高程level - minLon: number = null; - minLat: number = null; - maxLon: number = null; - maxLat: number = null; - minX: number = null; - minY: number = null; - maxX: number = null; - maxY: number = null; - segment: number = 1; - elevationInfo: any = null; - - //args中包含level、row、column、url即可 - constructor(args: any) { - super(null); - this.createVerticeData(args); - } - - createVerticeData(args: any) { - if (!args) { - return; - } - this.setTileInfo(args); - this.checkTerrain(); - } - - // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 - setTileInfo(args: any) { - this.level = args.level; - this.row = args.row; - this.column = args.column; - this.url = args.url; - this.elevationLevel = Elevation.getAncestorElevationLevel(this.level); - //经纬度范围 - var Egeo = MathUtils.getTileGeographicEnvelopByGrid(this.level, this.row, this.column); - this.minLon = Egeo.minLon; - this.minLat = Egeo.minLat; - this.maxLon = Egeo.maxLon; - this.maxLat = Egeo.maxLat; - var minCoord = MathUtils.degreeGeographicToWebMercator(this.minLon, this.minLat); - var maxCoord = MathUtils.degreeGeographicToWebMercator(this.maxLon, this.maxLat); - //投影坐标范围 - this.minX = minCoord[0]; - this.minY = minCoord[1]; - this.maxX = maxCoord[0]; - this.maxY = maxCoord[1]; - var matArgs = { - level: this.level, - url: this.url - }; - this.material = new TileMaterial(matArgs); - } - - /** - * 判断是否满足现实Terrain的条件,若满足则转换为三维地形 - * 条件: - * 1.当前显示的是GlobeTile - * 2.该切片的level大于TERRAIN_LEVEL - * 3.pich不为90 - * 4.当前切片的高程数据存在 - * 5.如果bForce为true,则表示强制显示为三维,不考虑level - */ - checkTerrain(bForce: boolean = false) { - var globe = Kernel.globe; - var a = bForce === true ? true : this.level >= Kernel.TERRAIN_LEVEL; - var shouldShowTerrain = this.type != Enum.TERRAIN_TILE && a && globe && globe.camera && globe.camera.pitch != 90; - if (shouldShowTerrain) { - //应该以TerrainTile显示 - if (!this.elevationInfo) { - this.elevationInfo = Elevation.getExactElevation(this.level, this.row, this.column); - - // if(this.level - this.elevationLevel == 1){ - // //当该level与其elevationLevel只相差一级时,可以使用推倒的高程数据 - // this.elevationInfo = Elevation.getElevation(this.level,this.row,this.column); - // if(this.elevationInfo){ - // console.log("Tile("+this.level+","+this.row+","+this.column+");sourceLevel:"+this.elevationInfo.sourceLevel+";elevationLevel:"+this.elevationLevel); - // } - // } - // else{ - // //否则使用准确的高程数据 - // this.elevationInfo = Elevation.getExactElevation(this.level,this.row,this.column); - // } - } - var canShowTerrain = this.elevationInfo ? true : false; - if (canShowTerrain) { - //能够显示为TerrainTile - this.handleTerrainTile(); - } else { - //不能够显示为TerrainTile - this.visible = false; - //this.handleGlobeTile(); - } - } else { - if (this.type == Enum.UNKNOWN) { - //初始type为UNKNOWN,还未初始化buffer,应该显示为GlobeTile - this.handleGlobeTile(); - } - } - } - - //处理球面的切片 - handleGlobeTile() { - this.type = Enum.GLOBE_TILE; - if (this.level < Kernel.BASE_LEVEL) { - var changeLevel = Kernel.BASE_LEVEL - this.level; - this.segment = Math.pow(2, changeLevel); - } else { - this.segment = 1; - } - this.handleTile(); - } - - //处理地形的切片 - handleTerrainTile() { - this.type = Enum.TERRAIN_TILE; - this.segment = 10; - this.handleTile(); - }; - - //如果是GlobeTile,那么elevations为null - //如果是TerrainTile,那么elevations是一个一维数组,大小是(segment+1)*(segment+1) - handleTile() { - this.visible = true; - var vertices:number[] = []; - var indices:number[] = []; - var textureCoords:number[] = []; - - var deltaX = (this.maxX - this.minX) / this.segment; - var deltaY = (this.maxY - this.minY) / this.segment; - var deltaTextureCoord = 1.0 / this.segment; - var changeElevation = this.type == Enum.TERRAIN_TILE && this.elevationInfo; - //level不同设置的半径也不同 - var levelDeltaR = 0; //this.level * 100; - //对WebMercator投影进行等间距划分格网 - var mercatorXs:number[] = []; //存储从最小的x到最大x的分割值 - var mercatorYs:number[] = []; //存储从最大的y到最小的y的分割值 - var textureSs:number[] = []; //存储从0到1的s的分割值 - var textureTs:number[] = []; //存储从1到0的t的分割值 - var i:number, j:number; - - for (i = 0; i <= this.segment; i++) { - mercatorXs.push(this.minX + i * deltaX); - mercatorYs.push(this.maxY - i * deltaY); - var b = i * deltaTextureCoord; - textureSs.push(b); - textureTs.push(1 - b); - } - //从左上到右下遍历填充vertices和textureCoords:从最上面一行开始自左向右遍历一行,然后再以相同的方式遍历下面一行 - for (i = 0; i <= this.segment; i++) { - for (j = 0; j <= this.segment; j++) { - var merX = mercatorXs[j]; - var merY = mercatorYs[i]; - var ele = changeElevation ? this.elevationInfo.elevations[(this.segment + 1) * i + j] : 0; - var lonlat = MathUtils.webMercatorToDegreeGeographic(merX, merY); - var p = MathUtils.geographicToCartesianCoord(lonlat[0], lonlat[1], Kernel.EARTH_RADIUS + ele + levelDeltaR).getArray(); - vertices = vertices.concat(p); //顶点坐标 - textureCoords = textureCoords.concat(textureSs[j], textureTs[i]); //纹理坐标 - } - } - - //从左上到右下填充indices - //添加的点的顺序:左上->左下->右下->右上 - //0 1 2; 2 3 0; - /*对于一个面从外面向里面看的绘制顺序 - * 0 3 - * - * 1 2*/ - for (i = 0; i < this.segment; i++) { - for (j = 0; j < this.segment; j++) { - var idx0 = (this.segment + 1) * i + j; - var idx1 = (this.segment + 1) * (i + 1) + j; - var idx2 = idx1 + 1; - var idx3 = idx0 + 1; - indices = indices.concat(idx0, idx1, idx2); // 0 1 2 - indices = indices.concat(idx2, idx3, idx0); // 2 3 0 - } - } - - // if(changeElevation){ - // //添加坐标原点的数据 - // var originVertice = [0,0,0]; - // var originTexture = [0,0]; - // vertices = vertices.concat(originVertice); - // textureCoords = textureCoords.concat(originTexture); - // - // var idxOrigin = (this.segment+1)*(this.segment+1); - // var idxLeftTop = 0; - // var idxRightTop = this.segment; - // var idxRightBottom = (this.segment+1)*(this.segment+1)-1; - // var idxLeftBottom = idxRightBottom - this.segment; - // indices = indices.concat(idxLeftTop,idxOrigin,idxLeftBottom); - // indices = indices.concat(idxRightTop,idxOrigin,idxLeftTop); - // indices = indices.concat(idxRightBottom,idxOrigin,idxRightTop); - // indices = indices.concat(idxLeftBottom,idxOrigin,idxRightBottom); - // } - - var infos = { - vertices: vertices, - indices: indices, - textureCoords: textureCoords - }; - this.setBuffers(infos); - } - - //重写Object3D的destroy方法 - destroy() { - super.destroy(); - this.subTiledLayer = null; - } -} - -export = Tile; \ No newline at end of file diff --git a/src/world/TileGrid.ts b/src/world/TileGrid.ts index 843c976..3f09444 100644 --- a/src/world/TileGrid.ts +++ b/src/world/TileGrid.ts @@ -1,7 +1,7 @@ /// import Kernel = require('./Kernel'); import Utils = require('./Utils'); -import MathUtils = require('./Math'); +import MathUtils = require('./math/Math'); class TileGrid { static LEFT_TOP = "LEFT_TOP"; diff --git a/src/world/TileMaterial.ts b/src/world/TileMaterial.ts deleted file mode 100644 index f56afa1..0000000 --- a/src/world/TileMaterial.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// -import TextureMaterial= require('./TextureMaterial'); -import ImageUtils = require('./Image'); - -class TileMaterial extends TextureMaterial{ - level: number; - - constructor(args?: any){ - super(args); - if (args) { - if (!args.image && typeof args.url === "string") { - var tileImage = ImageUtils.get(args.url); - if (tileImage) { - args.image = tileImage; - delete args.url; - } - } - this.level = typeof args.level == "number" && args.level >= 0 ? args.level : 20; - } - } - - onLoad() { - if (this.level <= ImageUtils.MAX_LEVEL) { - ImageUtils.add(this.image.src, this.image); - } - super.onLoad(); - } -} - -export = TileMaterial; \ No newline at end of file diff --git a/src/world/TiledLayer.ts b/src/world/TiledLayer.ts deleted file mode 100644 index 598f676..0000000 --- a/src/world/TiledLayer.ts +++ /dev/null @@ -1,54 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Object3DComponents = require('./Object3DComponents'); -import SubTiledLayer = require('./SubTiledLayer'); - -abstract class TiledLayer extends Object3DComponents { - //重写 - add(subTiledLayer: SubTiledLayer) { - super.add(subTiledLayer); - subTiledLayer.tiledLayer = this; - } - - protected wrapUrlWithProxy(url: string): string{ - if(Kernel.proxy){ - return Kernel.proxy + "?" + url; - } - return url; - } - - //根据切片的层级以及行列号获取图片的url,抽象方法,供子类实现 - abstract getImageUrl(level: number, row: number, column: number): string - - //根据传入的level更新SubTiledLayer的数量 - updateSubLayerCount(level: number) { - var subLayerCount = this.children.length; - var deltaLevel = level + 1 - subLayerCount; - var i: number, subLayer: SubTiledLayer; - if (deltaLevel > 0) { - //需要增加子图层 - for (i = 0; i < deltaLevel; i++) { - var args = { - level: i + subLayerCount - }; - subLayer = new SubTiledLayer(args); - this.add(subLayer); - } - } else if (deltaLevel < 0) { - //需要删除多余的子图层 - deltaLevel *= -1; - for (i = 0; i < deltaLevel; i++) { - var removeLevel = this.children.length - 1; - //第0级和第1级不删除 - if (removeLevel >= 2) { - subLayer = this.children[removeLevel]; - this.remove(subLayer); - } else { - break; - } - } - } - } -} - -export = TiledLayer; \ No newline at end of file diff --git a/src/world/VertexBufferObject.ts b/src/world/VertexBufferObject.ts new file mode 100644 index 0000000..be2006b --- /dev/null +++ b/src/world/VertexBufferObject.ts @@ -0,0 +1,42 @@ +/// +import Kernel = require("./Kernel"); + +class VertexBufferObject{ + buffer: WebGLBuffer; + + constructor(public target: number){ + //target: ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER + this.buffer = Kernel.gl.createBuffer(); + } + + bind(){ + Kernel.gl.bindBuffer(this.target, this.buffer); + } + + unbind(){ + Kernel.gl.bindBuffer(this.target, null); + } + + bufferData(data: number[], usage: number, hasBinded: boolean = false){ + if(!hasBinded){ + this.bind(); + } + + var gl = Kernel.gl; + + if(this.target === gl.ARRAY_BUFFER){ + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), usage); + }else if(this.target === gl.ELEMENT_ARRAY_BUFFER){ + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), usage); + } + } + + destroy(){ + if(this.buffer){ + Kernel.gl.deleteBuffer(this.buffer); + } + this.buffer = null; + } +} + +export = VertexBufferObject; \ No newline at end of file diff --git a/src/world/Vertice.ts b/src/world/Vertice.ts deleted file mode 100644 index c1b6cf7..0000000 --- a/src/world/Vertice.ts +++ /dev/null @@ -1,37 +0,0 @@ -/// - -class Vertice{ - constructor(public x = 0, public y = 0, public z = 0){} - - // minus(otherVertice: Vertice) : Vector { - // var x = this.x - otherVertice.x; - // var y = this.y - otherVertice.y; - // var z = this.z - otherVertice.z; - // return new Vector(x, y, z); - // } - - // plus(otherVector: Vector) : Vertice { - // var x = this.x + otherVector.x; - // var y = this.y + otherVector.y; - // var z = this.z + otherVector.z; - // return new Vertice(x, y, z); - // } - - // getVector(): Vector { - // return new Vector(this.x, this.y, this.z); - // } - - getArray(): number[] { - return [this.x, this.y, this.z]; - } - - clone(): Vertice { - return new Vertice(this.x, this.y, this.z); - } - - getOpposite(): Vertice { - return new Vertice(-this.x, -this.y, -this.z); - } -} - -export = Vertice; \ No newline at end of file diff --git a/src/world/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts new file mode 100644 index 0000000..ab1d760 --- /dev/null +++ b/src/world/geometries/Atmosphere.ts @@ -0,0 +1,72 @@ +/// + +import Kernel = require("../Kernel"); +import MeshVertice = require("./MeshVertice"); +import Triangle = require("./Triangle"); +import Mesh = require("./Mesh"); +import Vertice = require("../math/Vertice"); +import Matrix = require("../math/Matrix"); + +class Atmosphere extends Mesh { + private readonly segment: number = 360; + private readonly radius1: number = Kernel.EARTH_RADIUS * 0.99; + private readonly radius2: number = Kernel.EARTH_RADIUS * 1.01; + + constructor() { + super(); + this.buildTriangles(); + } + + buildTriangles(){ + this.vertices = []; + this.triangles = []; + + var mat1 = new Matrix(); + mat1.setPosition(new Vertice(0, this.radius1, 0)); + var meshVertices1: MeshVertice[] = []; + + var mat2 = new Matrix(); + mat2.setPosition(new Vertice(0, this.radius2, 0)); + var meshVertices2: MeshVertice[] = []; + + var deltaRadian: number = - Math.PI * 2 / this.segment; + var deltaS: number = 1.0 / this.segment; + var u: number = 0; + + for(var i = 0; i <= this.segment; i++){ + u = deltaS * i; + if(u > 1){ + u = 1; + } + //don't flip Y + //small radius, v is always 1 + meshVertices1.push(new MeshVertice({ + i: i, + p: mat1.getPosition().getArray(), + uv: [u, 1] + })); + + //big radius, v is always 0 + meshVertices2.push(new MeshVertice({ + i: this.segment + 1 + i, + p: mat2.getPosition().getArray(), + uv: [u, 0] + })); + + if(i > 0){ + var vLeftTop = meshVertices2[i-1]; + var vLeftBottom = meshVertices1[i-1]; + var vRightTop = meshVertices2[i]; + var vRightBottom = meshVertices1[i]; + this.triangles.push(...Triangle.assembleQuad(vLeftTop, vLeftBottom, vRightTop, vRightBottom)); + } + + mat1.worldRotateZ(deltaRadian); + mat2.worldRotateZ(deltaRadian); + } + + this.vertices.push(...meshVertices1, ...meshVertices2); + } +} + +export = Atmosphere; \ No newline at end of file diff --git a/src/world/geometries/Box.ts b/src/world/geometries/Box.ts new file mode 100644 index 0000000..02bbd2b --- /dev/null +++ b/src/world/geometries/Box.ts @@ -0,0 +1,147 @@ +/// + +import Vertice = require("./MeshVertice"); +import Triangle = require("./Triangle"); +import Mesh = require("./Mesh"); + +class Box extends Mesh { + constructor(public length: number, public width: number, public height: number) { + super(); + this.buildTriangles(); + } + + buildTriangles() { + this.vertices = []; + this.triangles = []; + + var halfLength = this.length / 2; + var halfHeight = this.height / 2; + var halfWidth = this.width / 2; + + /* + B1---- B3 + / | / | + F1----F3 | + | B2- |--B4 + |/ | / + F2----F4 + */ + + //前面四个顶点 + var vF1 = [-halfLength, halfHeight, halfWidth]; //前面左上角点 F1,索引0 + var vF2 = [-halfLength, -halfHeight, halfWidth]; //前面左下角点 F2,索引1 + var vF3 = [halfLength, halfHeight, halfWidth]; //前面右上角点 F3,索引2 + var vF4 = [halfLength, -halfHeight, halfWidth]; //前面右下角点 F4,索引3 + + //后面四个顶点 + var vB1 = [-halfLength, halfHeight, -halfWidth]; //后面左上角点 B1,索引4 + var vB2 = [-halfLength, -halfHeight, -halfWidth]; //后面左下角点 B2,索引5 + var vB3 = [halfLength, halfHeight, -halfWidth]; //后面右上角点 B3,索引6 + var vB4 = [halfLength, -halfHeight, -halfWidth]; //后面右下角点 B4,索引7 + + /*对于一个面从外面向里面看的绘制顺序 + * 0 2 + * + * 1 3*/ + //0,1,2; 2,1,3 + + var index = 0; + + //加入前面四个顶点,索引号:0,1,2,3 + var pzResult = this._buildPlane(index, vF1, vF2, vF3, vF4, [0, 0, 1]); + this.vertices = this.vertices.concat(pzResult.vertices); + this.triangles = this.triangles.concat(pzResult.triangles); + index += 4; + + //加入右面四个顶点,索引号:4,5,6,7 + var pxResult = this._buildPlane(index, vF3, vF4, vB3, vB4, [1, 0, 0]); + this.vertices = this.vertices.concat(pxResult.vertices); + this.triangles = this.triangles.concat(pxResult.triangles); + index += 4; + + //加入后面四个顶点,索引号:8,9,10,11 + var nzResult = this._buildPlane(index, vB3, vB4, vB1, vB2, [0, 0, -1]); + this.vertices = this.vertices.concat(nzResult.vertices); + this.triangles = this.triangles.concat(nzResult.triangles); + index += 4; + + //加入左面四个顶点,索引号:12,13,14,15 + var nxResult = this._buildPlane(index, vB1, vB2, vF1, vF2, [-1, 0, 0]); + this.vertices = this.vertices.concat(nxResult.vertices); + this.triangles = this.triangles.concat(nxResult.triangles); + index += 4; + + //加入上面的四个顶点,索引号:16,17,18,19 + var pyResult = this._buildPlane(index, vB1, vF1, vB3, vF3, [0, 1, 0]); + this.vertices = this.vertices.concat(pyResult.vertices); + this.triangles = this.triangles.concat(pyResult.triangles); + index += 4; + + + //加入下面四个顶点,索引号:20,21,22,23 + var nyResult = this._buildPlane(index, vF2, vB2, vF4, vB4, [0, -1, 0]); + this.vertices = this.vertices.concat(nyResult.vertices); + this.triangles = this.triangles.concat(nyResult.triangles); + } + + _buildPlane(startIndex: number, pLT: number[], pLB: number[], pRT: number[], pRB: number[], nortal: number[]) { + /*对于一个面从外面向里面看的绘制顺序 + * 0 2 + * + * 1 3*/ + //0,1,2; 2,1,3 + + var result = { + vertices: [], + triangles: [] + }; + + //vertices + + var v0 = new Vertice({ + i: startIndex, + p: pLT, + uv: [0, 0], + n: nortal, + c: null + }); + + var v1 = new Vertice({ + i: startIndex + 1, + p: pLB, + uv: [0, 1], + n: nortal, + c: null + }); + + var v2 = new Vertice({ + i: startIndex + 2, + p: pRT, + uv: [1, 0], + n: nortal, + c: null + }); + + var v3 = new Vertice({ + i: startIndex + 3, + p: pRB, + uv: [1, 1], + n: nortal, + c: null + }); + + result.vertices = [v0, v1, v2, v3]; + + //triangles + + var tri0 = new Triangle(v0, v1, v2); + + var tri1 = new Triangle(v2, v1, v3); + + result.triangles = [tri0, tri1]; + + return result; + } +} + +export = Box; \ No newline at end of file diff --git a/src/world/geometries/Geometry.ts b/src/world/geometries/Geometry.ts new file mode 100644 index 0000000..f843a29 --- /dev/null +++ b/src/world/geometries/Geometry.ts @@ -0,0 +1,7 @@ +/// + +interface Geometry{ + destroy():void +} + +export = Geometry; \ No newline at end of file diff --git a/src/world/geometries/Marker.ts b/src/world/geometries/Marker.ts new file mode 100644 index 0000000..c76a9c0 --- /dev/null +++ b/src/world/geometries/Marker.ts @@ -0,0 +1,24 @@ +/// + +import Kernel = require("../Kernel"); +import Geometry = require('./Geometry'); +import VertexBufferObject = require("../VertexBufferObject"); + +class Marker implements Geometry{ + + vbo: VertexBufferObject; + + constructor(public x: number, public y: number, public z: number){ + this.vbo = new VertexBufferObject(Kernel.gl.ARRAY_BUFFER); + this.vbo.bind(); + this.vbo.bufferData([x,y,z], Kernel.gl.STATIC_DRAW, true); + this.vbo.unbind(); + } + + destroy(){ + this.vbo.destroy(); + this.vbo = null; + } +} + +export = Marker; \ No newline at end of file diff --git a/src/world/geometries/Mesh.ts b/src/world/geometries/Mesh.ts new file mode 100644 index 0000000..0c0dd4a --- /dev/null +++ b/src/world/geometries/Mesh.ts @@ -0,0 +1,154 @@ +/// +import Kernel = require("../Kernel"); +import Vertice = require("./MeshVertice"); +import Triangle = require("./Triangle"); +import Object3D = require("../Object3D"); +import VertexBufferObject = require("../VertexBufferObject"); + +class Mesh extends Object3D { + vertices: Vertice[]; + triangles: Triangle[]; + vbo: VertexBufferObject; + ibo: VertexBufferObject; + nbo: VertexBufferObject; + uvbo: VertexBufferObject; + cbo: VertexBufferObject; + + //set vertices and triangles + buildTriangles(){ + this.vertices = []; + this.triangles = []; + } + + calculateVBO(force:boolean = false) { + if (!this.vbo || force) { + var vboData:number[] = [], vertex:Vertice; + + for (var i = 0, length = this.vertices.length; i < length; i++) { + vertex = this.vertices[i]; + vboData.push(vertex.p[0]); + vboData.push(vertex.p[1]); + vboData.push(vertex.p[2]); + } + + if (!this.vbo) { + this.vbo = new VertexBufferObject(Kernel.gl.ARRAY_BUFFER); + } + this.vbo.bind(); + this.vbo.bufferData(vboData, Kernel.gl.STATIC_DRAW, true); + this.vbo.unbind(); + } + return this.vbo; + } + + calculateIBO(force:boolean = false) { + if (!this.ibo || force) { + var iboData:number[] = [], triangle:Triangle; + + for (var i = 0, length = this.triangles.length; i < length; i++) { + triangle = this.triangles[i]; + iboData.push(triangle.v1.i); + iboData.push(triangle.v2.i); + iboData.push(triangle.v3.i); + } + + if (!this.ibo) { + this.ibo = new VertexBufferObject(Kernel.gl.ELEMENT_ARRAY_BUFFER); + } + this.ibo.bind(); + this.ibo.bufferData(iboData, Kernel.gl.STATIC_DRAW, true); + this.ibo.unbind(); + } + return this.ibo; + } + + calculateNBO(force:boolean = false) { + if (!this.nbo || force) { + var nboData:number[] = [], vertex:Vertice; + + for (var i = 0, length = this.vertices.length; i < length; i++) { + vertex = this.vertices[i]; + nboData.push(vertex.n[0]); + nboData.push(vertex.n[1]); + nboData.push(vertex.n[2]); + } + + if (!this.nbo) { + this.nbo = new VertexBufferObject(Kernel.gl.ARRAY_BUFFER); + } + this.nbo.bind(); + this.nbo.bufferData(nboData, Kernel.gl.STATIC_DRAW, true); + this.nbo.unbind(); + } + return this.nbo; + } + + calculateUVBO(force:boolean = false) { + if (!this.uvbo || force) { + var uvboData:number[] = [], vertex:Vertice; + + for (var i = 0, length = this.vertices.length; i < length; i++) { + vertex = this.vertices[i]; + uvboData.push(vertex.uv[0]); + uvboData.push(vertex.uv[1]); + } + + if (!this.uvbo) { + this.uvbo = new VertexBufferObject(Kernel.gl.ARRAY_BUFFER); + } + this.uvbo.bind(); + this.uvbo.bufferData(uvboData, Kernel.gl.STATIC_DRAW, true); + this.uvbo.unbind(); + } + return this.uvbo; + } + + calculateCBO(force:boolean = false) { + if (!this.cbo || force) { + var cboData:number[] = [], vertex:Vertice; + + for (var i = 0, length = this.vertices.length; i < length; i++) { + vertex = this.vertices[i]; + cboData.push(vertex.c[0]); + cboData.push(vertex.c[1]); + cboData.push(vertex.c[2]); + } + + if (!this.cbo) { + this.cbo = new VertexBufferObject(Kernel.gl.ARRAY_BUFFER); + } + this.cbo.bind(); + this.cbo.bufferData(cboData, Kernel.gl.STATIC_DRAW, true); + this.cbo.unbind(); + } + return this.cbo; + } + + destroy() { + if (this.vbo) { + this.vbo.destroy(); + } + if (this.ibo) { + this.ibo.destroy(); + } + if (this.nbo) { + this.nbo.destroy(); + } + if (this.cbo) { + this.cbo.destroy(); + } + if (this.uvbo) { + this.uvbo.destroy(); + } + + this.vbo = null; + this.ibo = null; + this.nbo = null; + this.cbo = null; + this.uvbo = null; + this.vertices = []; + this.triangles = []; + } +} + +export = Mesh; \ No newline at end of file diff --git a/src/world/geometries/MeshVertice.ts b/src/world/geometries/MeshVertice.ts new file mode 100644 index 0000000..8bc9f64 --- /dev/null +++ b/src/world/geometries/MeshVertice.ts @@ -0,0 +1,19 @@ +/// +class MeshVertice{ + p:number[]; + n:number[]; + uv:number[]; + c:number[]; + i:number; + + constructor(args:any){ + this.i = args.i;//index + this.p = args.p;//[x,y,z] + this.uv = args.uv;//[s,t] + + this.n = args.n;//[x,y,z] + this.c = args.c;//[r,g,b] + } +} + +export = MeshVertice; \ No newline at end of file diff --git a/src/world/geometries/TileGeometry.ts b/src/world/geometries/TileGeometry.ts new file mode 100644 index 0000000..37f3961 --- /dev/null +++ b/src/world/geometries/TileGeometry.ts @@ -0,0 +1,13 @@ +/// + +import Vertice = require("./MeshVertice"); +import Triangle = require("./Triangle"); +import Mesh = require("./Mesh"); + +class TileGeometry extends Mesh { + constructor(public vertices: Vertice[], public triangles: Triangle[]) { + super(); + } +} + +export = TileGeometry; \ No newline at end of file diff --git a/src/world/geometries/Triangle.ts b/src/world/geometries/Triangle.ts new file mode 100644 index 0000000..b4081ad --- /dev/null +++ b/src/world/geometries/Triangle.ts @@ -0,0 +1,17 @@ +/// +import Vertice = require("./MeshVertice"); + +class Triangle{ + + constructor(public v1: Vertice, public v2: Vertice, public v3: Vertice){} + + setColor(c: number[]){ + this.v1.c = this.v2.c = this.v3.c = c; + } + + static assembleQuad(leftTop: Vertice, leftBottom: Vertice, rightTop: Vertice, rightBottom: Vertice): Triangle[]{ + return [new Triangle(leftTop, leftBottom, rightTop), new Triangle(rightTop, leftBottom, rightBottom)]; + } +} + +export = Triangle; \ No newline at end of file diff --git a/src/world/graphics/Atmosphere.ts b/src/world/graphics/Atmosphere.ts new file mode 100644 index 0000000..e70b3d1 --- /dev/null +++ b/src/world/graphics/Atmosphere.ts @@ -0,0 +1,56 @@ +/// + +import Kernel = require("../Kernel"); +import MeshGraphic = require('./MeshGraphic'); +import AtmosphereGeometry = require("../geometries/Atmosphere"); +import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); +import Camera from "../Camera"; +import Vector = require("../math/Vector"); + +class Atmosphere extends MeshGraphic { + private constructor(public geometry: AtmosphereGeometry, public material: MeshTextureMaterial){ + super(geometry, material); + } + + static getInstance(): Atmosphere{ + var geometry = new AtmosphereGeometry(); + var imageUrl = "/WebGlobe/src/world/images/atmosphere.png"; + var material = new MeshTextureMaterial(imageUrl, false); + return new Atmosphere(geometry, material); + } + + onDraw(camera: Camera){ + var gl = Kernel.gl; + gl.disable(gl.DEPTH_TEST); + gl.depthMask(false); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + this.geometry.getMatrix().setUnitMatrix(); + + //根据Camera动态调整Atmosphere的matrix,使其一直垂直面向摄像机 + var R = Kernel.EARTH_RADIUS; + var distanceCamera2Origin = camera.getDistance2EarthOrigin(); + var distanceCamera2EarthTangent = Math.sqrt(distanceCamera2Origin * distanceCamera2Origin - R * R); + var sinθ = distanceCamera2EarthTangent / distanceCamera2Origin; + var distanceCamera2Atmosphere = distanceCamera2EarthTangent * sinθ; + var vector = camera.getLightDirection().setLength(distanceCamera2Atmosphere); + //计算出Atmosphere新的位置 + var atmosphereNewPosition = Vector.verticePlusVector(camera.getPosition(), vector); + this.geometry.setPosition(atmosphereNewPosition); + //将Atmosphere的坐标轴方向设置的与Camera相同,这样使其垂直面向摄像机 + this.geometry.setVectorX(camera.getVectorX()); + this.geometry.setVectorY(camera.getVectorY()); + this.geometry.setVectorZ(camera.getVectorZ()); + //缩小Atmosphere使其能够正好将视线与球的圆切面包围 + this.geometry.localScale(sinθ, sinθ, sinθ); + + super.onDraw(camera); + + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.disable(gl.BLEND); + } +} + +export = Atmosphere; \ No newline at end of file diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts new file mode 100644 index 0000000..24c8b47 --- /dev/null +++ b/src/world/graphics/Graphic.ts @@ -0,0 +1,66 @@ +/// + +import Kernel = require("../Kernel"); +import Geometry = require("../geometries/Geometry"); +import Material = require("../materials/Material"); +import Program = require("../Program"); +import Camera from "../Camera"; +import GraphicGroup = require("../GraphicGroup"); + +interface GraphicOptions{ + geometry: Geometry; + material: Material; + parent: GraphicGroup; + visible?: boolean; +} + +abstract class Graphic{ + id: number; + visible: boolean = true; + parent: GraphicGroup; + program: Program; + + constructor(public geometry: Geometry, public material: Material){ + this.id = ++Kernel.idCounter; + this.parent = null; + this.program = Program.getProgram(this); + } + + setVisible(visible: boolean){ + this.visible = visible; + } + + abstract createProgram(): Program + + getProgramType() { + return this.material.getType(); + } + + isReady(): boolean{ + return this.geometry && this.material && this.material.isReady(); + } + + isDrawable(): boolean{ + return this.visible && this.isReady(); + } + + draw(camera: Camera){ + if(this.isDrawable()){ + this.program.use(); + this.onDraw(camera); + } + } + + abstract onDraw(camera: Camera):void + + destroy(){ + this.parent = null; + //释放显卡中的资源 + this.geometry.destroy(); + this.material.destroy(); + this.geometry = null; + this.material = null; + } +} + +export = Graphic; \ No newline at end of file diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts new file mode 100644 index 0000000..3792a15 --- /dev/null +++ b/src/world/graphics/MeshGraphic.ts @@ -0,0 +1,103 @@ +/// + +import Kernel = require("../Kernel"); +import Program = require("../Program"); +import Graphic = require("./Graphic"); +import Mesh = require("../geometries/Mesh"); +import MeshTextureMaterial = require("../materials/MeshTextureMaterial"); +import Camera from "../Camera"; + +const vs = +` +attribute vec3 aPosition; +attribute vec2 aUV; +varying vec2 vUV; +uniform mat4 uPMVMatrix; + +void main() +{ + gl_Position = uPMVMatrix * vec4(aPosition,1.0); + vUV = aUV; +} +`; + +const fs = +` +precision mediump float; +varying vec2 vUV; +uniform sampler2D uSampler; + +void main() +{ + gl_FragColor = texture2D(uSampler, vec2(vUV.s, vUV.t)); +} +`; + +class MeshGraphic extends Graphic { + constructor(public geometry: Mesh, public material: MeshTextureMaterial){ + super(geometry, material); + this.geometry.calculateVBO(); + this.geometry.calculateIBO(); + this.geometry.calculateUVBO(); + } + + isGeometryReady():boolean{ + return !!this.geometry.vbo && !!this.geometry.ibo && !!this.geometry.uvbo; + } + + isReady():boolean{ + return this.isGeometryReady() && super.isReady(); + } + + createProgram(): Program{ + return new Program(this.getProgramType(), vs, fs); + } + + _drawTextureMaterial(program: any) { + var gl = Kernel.gl; + + //set aUV + var locUV = program.getAttribLocation('aUV'); + program.enableVertexAttribArray('aUV'); + this.geometry.uvbo.bind(); + gl.vertexAttribPointer(locUV, 2, gl.FLOAT, false, 0, 0); + + //set uSampler + var locSampler = program.getUniformLocation('uSampler'); + gl.activeTexture(gl.TEXTURE0); + //world.Cache.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.material.texture); + gl.uniform1i(locSampler, 0); + } + + onDraw(camera: Camera) { + var gl = Kernel.gl; + + //aPosition + var locPosition = this.program.getAttribLocation('aPosition'); + this.program.enableVertexAttribArray('aPosition'); + this.geometry.vbo.bind(); + gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 0, 0); + + //uPMVMatrix + var pmvMatrix = camera.getProjViewMatrixForDraw().multiplyMatrix(this.geometry.getMatrix()); + var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); + gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); + + this._drawTextureMaterial(this.program); + + //设置索引,但不用往shader中赋值 + this.geometry.ibo.bind(); + + //绘图 + var count = this.geometry.triangles.length * 3; + gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, 0); + + //释放当前绑定对象 + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); + } +} + +export = MeshGraphic; \ No newline at end of file diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts new file mode 100644 index 0000000..a584034 --- /dev/null +++ b/src/world/graphics/Poi.ts @@ -0,0 +1,85 @@ +/// + +import Kernel = require("../Kernel"); +import Graphic = require('./Graphic'); +import Marker = require('../geometries/Marker'); +import PoiMaterial = require('../materials/PoiMaterial'); +import Program = require("../Program"); +import Camera from "../Camera"; + +const vs = +` +attribute vec3 aPosition; +uniform mat4 uPMVMatrix; +uniform float uSize; + +void main(void) { + gl_Position = uPMVMatrix * vec4(aPosition, 1.0); + gl_PointSize = uSize; +} +`; + +//http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 +//gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y)); +const fs = +` +precision mediump float; +uniform sampler2D uSampler; + +void main() +{ + gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, gl_PointCoord.y)); +} +`; + +class Poi extends Graphic { + constructor(public geometry: Marker, public material: PoiMaterial){ + super(geometry, material); + } + + createProgram(){ + return new Program(this.getProgramType(), vs, fs); + } + + onDraw(camera: Camera){ + var gl = Kernel.gl; + + //gl.disable(gl.DEPTH_TEST); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + //aPosition + var locPosition = this.program.getAttribLocation('aPosition'); + this.program.enableVertexAttribArray('aPosition'); + this.geometry.vbo.bind(); + gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 0, 0); + + //uPMVMatrix + var pmvMatrix = camera.getProjViewMatrixForDraw(); + var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); + gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); + + //uSize + var locSize = this.program.getUniformLocation('uSize'); + gl.uniform1f(locSize, this.material.size); + + //set uSampler + var locSampler = this.program.getUniformLocation('uSampler'); + gl.activeTexture(gl.TEXTURE0); + //world.Cache.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.material.texture); + gl.uniform1i(locSampler, 0); + + //绘图,1表示1个点 + gl.drawArrays(gl.POINTS, 0, 1); + + //释放当前绑定对象 + //gl.enable(gl.DEPTH_TEST); + gl.disable(gl.BLEND); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); + } +} + +export = Poi; \ No newline at end of file diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts new file mode 100644 index 0000000..87933c8 --- /dev/null +++ b/src/world/graphics/Tile.ts @@ -0,0 +1,175 @@ +/// +import Kernel = require('../Kernel'); +import Enum = require('../Enum'); +import MathUtils = require('../math/Math'); +import MeshGraphic = require('../graphics/MeshGraphic'); +import TileMaterial = require('../materials/TileMaterial'); +import TileGeometry = require("../geometries/TileGeometry"); +import Vertice = require("../geometries/MeshVertice"); +import Triangle = require("../geometries/Triangle"); +import SubTiledLayer = require("../layers/SubTiledLayer"); + +class TileInfo { + //type如果是GLOBE_TILE,表示其buffer已经设置为一般形式 + //type如果是TERRAIN_TILE,表示其buffer已经设置为高程形式 + //type如果是UNKNOWN,表示buffer没设置 + type: number = Enum.UNKNOWN; + minLon: number = null; + minLat: number = null; + maxLon: number = null; + maxLat: number = null; + minX: number = null; + minY: number = null; + maxX: number = null; + maxY: number = null; + segment: number = 1; + elevationInfo: any = null; + geometry: TileGeometry; + material: TileMaterial; + visible: boolean; + + constructor(public level: number, public row: number, public column: number, public url: string) { + this._setTileInfo(); + if (this.type == Enum.UNKNOWN) { + //初始type为UNKNOWN,还未初始化buffer,应该显示为GlobeTile + this._handleGlobeTile(); + } + this.material = new TileMaterial(this.level, this.url); + } + + // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 + _setTileInfo() { + //经纬度范围 + var Egeo = MathUtils.getTileGeographicEnvelopByGrid(this.level, this.row, this.column); + this.minLon = Egeo.minLon; + this.minLat = Egeo.minLat; + this.maxLon = Egeo.maxLon; + this.maxLat = Egeo.maxLat; + var minCoord = MathUtils.degreeGeographicToWebMercator(this.minLon, this.minLat); + var maxCoord = MathUtils.degreeGeographicToWebMercator(this.maxLon, this.maxLat); + + //投影坐标范围 + this.minX = minCoord[0]; + this.minY = minCoord[1]; + this.maxX = maxCoord[0]; + this.maxY = maxCoord[1]; + } + + //处理球面的切片 + _handleGlobeTile() { + this.type = Enum.GLOBE_TILE; + if (this.level < Kernel.BASE_LEVEL) { + var changeLevel = Kernel.BASE_LEVEL - this.level; + this.segment = Math.pow(2, changeLevel); + } else { + this.segment = 1; + } + this._handleTile(); + } + + //如果是GlobeTile,那么elevations为null + //如果是TerrainTile,那么elevations是一个一维数组,大小是(segment+1)*(segment+1) + _handleTile() { + this.visible = true; + var verticeArray: Vertice[] = []; + var triangleArray: Triangle[] = []; + var vertices: number[] = []; + var indices: number[] = []; + var textureCoords: number[] = []; + + var deltaX = (this.maxX - this.minX) / this.segment; + var deltaY = (this.maxY - this.minY) / this.segment; + var deltaTextureCoord = 1.0 / this.segment; + var changeElevation = this.type === Enum.TERRAIN_TILE && this.elevationInfo; + //level不同设置的半径也不同 + var levelDeltaR = 0;//this.level * 2; + //对WebMercator投影进行等间距划分格网 + var mercatorXs: number[] = []; //存储从最小的x到最大x的分割值 + var mercatorYs: number[] = []; //存储从最大的y到最小的y的分割值 + var textureSs: number[] = []; //存储从0到1的s的分割值 + var textureTs: number[] = []; //存储从1到0的t的分割值 + var i: number, j: number; + + for (i = 0; i <= this.segment; i++) { + mercatorXs.push(this.minX + i * deltaX); + mercatorYs.push(this.maxY - i * deltaY); + var b = i * deltaTextureCoord; + textureSs.push(b); + textureTs.push(1 - b); + } + + //从左上到右下遍历填充vertices和textureCoords + //从最上面一行开始自左向右遍历一行,然后再以相同的方式遍历下面一行 + var index = 0; + for (i = 0; i <= this.segment; i++) { + for (j = 0; j <= this.segment; j++) { + var merX = mercatorXs[j]; + var merY = mercatorYs[i]; + var ele:number = 0;//高程 + var lonlat = MathUtils.webMercatorToDegreeGeographic(merX, merY); + var p = MathUtils.geographicToCartesianCoord(lonlat[0], lonlat[1], Kernel.EARTH_RADIUS + ele + levelDeltaR).getArray(); + vertices = vertices.concat(p); //顶点坐标 + textureCoords = textureCoords.concat(textureSs[j], textureTs[i]); //纹理坐标 + var v = new Vertice({ + p: p, + i: index, + uv: [textureSs[j], textureTs[i]] + }); + verticeArray.push(v); + index++; + } + } + + //从左上到右下填充indices + //添加的点的顺序:左上->左下->右下->右上 + //0 1 2; 2 3 0; + /*对于一个面从外面向里面看的绘制顺序 + * 0 3 + * + * 1 2*/ + for (i = 0; i < this.segment; i++) { + for (j = 0; j < this.segment; j++) { + var idx0 = (this.segment + 1) * i + j; + var idx1 = (this.segment + 1) * (i + 1) + j; + var idx2 = idx1 + 1; + var idx3 = idx0 + 1; + indices = indices.concat(idx0, idx1, idx2); // 0 1 2 + indices = indices.concat(idx2, idx3, idx0); // 2 3 0 + var v0: Vertice = verticeArray[idx0]; + var v1: Vertice = verticeArray[idx1]; + var v2: Vertice = verticeArray[idx2]; + var v3: Vertice = verticeArray[idx3]; + var triangle1 = new Triangle(v0, v1, v2); + var triangle2 = new Triangle(v2, v3, v0); + triangleArray.push(triangle1, triangle2); + } + } + + this.geometry = new TileGeometry(verticeArray, triangleArray); + } +} + +class Tile extends MeshGraphic { + subTiledLayer: SubTiledLayer; + + private constructor(public geometry: TileGeometry, public material: TileMaterial, public tileInfo: TileInfo) { + super(geometry, material); + } + + static getInstance(level: number, row: number, column: number, url: string) { + var tileInfo = new TileInfo(level, row, column, url); + return new Tile(tileInfo.geometry, tileInfo.material, tileInfo); + } + + isDrawable(){ + return this.tileInfo.visible && super.isDrawable(); + } + + //重写Object3D的destroy方法 + destroy() { + super.destroy(); + this.subTiledLayer = null; + } +} + +export = Tile; \ No newline at end of file diff --git a/src/world/images/1.png b/src/world/images/1.png new file mode 100644 index 0000000..8c5c15e Binary files /dev/null and b/src/world/images/1.png differ diff --git a/src/world/images/atmosphere.png b/src/world/images/atmosphere.png new file mode 100644 index 0000000..f1b1d02 Binary files /dev/null and b/src/world/images/atmosphere.png differ diff --git a/src/world/images/original.png b/src/world/images/original.png new file mode 100644 index 0000000..4f0a404 Binary files /dev/null and b/src/world/images/original.png differ diff --git a/src/world/images/pin.png b/src/world/images/pin.png new file mode 100644 index 0000000..727591d Binary files /dev/null and b/src/world/images/pin.png differ diff --git a/src/world/images/poi.png b/src/world/images/poi.png new file mode 100644 index 0000000..dc00c81 Binary files /dev/null and b/src/world/images/poi.png differ diff --git a/src/world/ArcGISTiledLayer.ts b/src/world/layers/ArcGISTiledLayer.ts similarity index 61% rename from src/world/ArcGISTiledLayer.ts rename to src/world/layers/ArcGISTiledLayer.ts index c410716..c2f1d81 100644 --- a/src/world/ArcGISTiledLayer.ts +++ b/src/world/layers/ArcGISTiledLayer.ts @@ -1,5 +1,5 @@ -/// -import Kernel = require("./Kernel"); +/// +import Kernel = require("../Kernel"); import TiledLayer = require("./TiledLayer"); class ArcGISTiledLayer extends TiledLayer{ @@ -7,7 +7,7 @@ class ArcGISTiledLayer extends TiledLayer{ super(); } - getImageUrl(level: number, row: number, column: number) { + getTileUrl(level: number, row: number, column: number) { var url = Kernel.proxy + "?" + this.url + "/tile/" + level + "/" + row + "/" + column; return this.wrapUrlWithProxy(url); } diff --git a/src/world/AutonaviTiledLayer.ts b/src/world/layers/AutonaviTiledLayer.ts similarity index 67% rename from src/world/AutonaviTiledLayer.ts rename to src/world/layers/AutonaviTiledLayer.ts index dbb797f..9822d81 100644 --- a/src/world/AutonaviTiledLayer.ts +++ b/src/world/layers/AutonaviTiledLayer.ts @@ -1,15 +1,17 @@ -/// -import Kernel = require('./Kernel'); +/// +import Kernel = require('../Kernel'); import TiledLayer = require('./TiledLayer'); class AutonaviTiledLayer extends TiledLayer{ - getImageUrl(level: number, row: number, column: number) { + + getTileUrl(level: number, row: number, column: number) { //使用代理 var sum = level + row + column; var serverIdx = 1 + sum % 4; //1、2、3、4 var url = "//webrd0" + serverIdx + ".is.autonavi.com/appmaptile?x=" + column + "&y=" + row + "&z=" + level + "&lang=zh_cn&size=1&scale=1&style=8"; return this.wrapUrlWithProxy(url); } + } export = AutonaviTiledLayer; \ No newline at end of file diff --git a/src/world/BingTiledLayer.ts b/src/world/layers/BingTiledLayer.ts similarity index 86% rename from src/world/BingTiledLayer.ts rename to src/world/layers/BingTiledLayer.ts index d024f8a..9bc4211 100644 --- a/src/world/BingTiledLayer.ts +++ b/src/world/layers/BingTiledLayer.ts @@ -1,10 +1,11 @@ -/// -import MathUtils = require('./Math'); +/// +import MathUtils = require('../math/Math'); import TiledLayer = require('./TiledLayer'); //Bing地图 class BingTiledLayer extends TiledLayer{ - getImageUrl(level: number, row: number, column: number): string { + + getTileUrl(level: number, row: number, column: number): string { var url = ""; var tileX = column; var tileY = row; @@ -41,6 +42,7 @@ class BingTiledLayer extends TiledLayer{ url = "//ecn.t" + serverIdx + ".tiles.virtualearth.net/tiles/h" + strMerge4 + ".jpeg?g=1239&mkt=en-us"; return url; } + } export = BingTiledLayer; \ No newline at end of file diff --git a/src/world/BlendTiledLayer.ts b/src/world/layers/BlendTiledLayer.ts similarity index 64% rename from src/world/BlendTiledLayer.ts rename to src/world/layers/BlendTiledLayer.ts index fab27d8..55bc61e 100644 --- a/src/world/BlendTiledLayer.ts +++ b/src/world/layers/BlendTiledLayer.ts @@ -1,17 +1,19 @@ -/// +/// import TiledLayer = require("./TiledLayer"); import NokiaTiledLayer = require("./NokiaTiledLayer"); import GoogleTiledLayer = require("./GoogleTiledLayer"); import OsmTiledLayer = require("./OsmTiledLayer"); class BlendTiledLayer extends TiledLayer { - getImageUrl(level: number, row: number, column: number): string { + + getTileUrl(level: number, row: number, column: number): string { var array = [NokiaTiledLayer, GoogleTiledLayer, OsmTiledLayer]; var sum = level + row + column; var idx = sum % 3; - var url = array[idx].prototype.getImageUrl.apply(this, arguments); + var url = array[idx].prototype.getTileUrl.apply(this, arguments); return url; } + } export = BlendTiledLayer; \ No newline at end of file diff --git a/src/world/GoogleTiledLayer.ts b/src/world/layers/GoogleTiledLayer.ts similarity index 68% rename from src/world/GoogleTiledLayer.ts rename to src/world/layers/GoogleTiledLayer.ts index 19d09d3..da4ced6 100644 --- a/src/world/GoogleTiledLayer.ts +++ b/src/world/layers/GoogleTiledLayer.ts @@ -1,13 +1,15 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class GoogleTiledLayer extends TiledLayer{ - getImageUrl(level: number, row: number, column: number) { + + getTileUrl(level: number, row: number, column: number) { var sum = level + row + column; var idx = 1 + sum % 3; 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"; return url; } + } export = GoogleTiledLayer; \ No newline at end of file diff --git a/src/world/NokiaTiledLayer.ts b/src/world/layers/NokiaTiledLayer.ts similarity index 79% rename from src/world/NokiaTiledLayer.ts rename to src/world/layers/NokiaTiledLayer.ts index b5bd3cd..b6e3529 100644 --- a/src/world/NokiaTiledLayer.ts +++ b/src/world/layers/NokiaTiledLayer.ts @@ -1,14 +1,16 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class NokiaTiledLayer extends TiledLayer{ - getImageUrl(level: number, row: number, column: number): string { + + getTileUrl(level: number, row: number, column: number): string { var sum = level + row + column; var idx = 1 + sum % 4; //1,2,3,4 //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 var url = "//"+idx+".base.maps.api.here.com/maptile/2.1/maptile/f1f4a211b3/normal.day/"+level+"/"+column+"/"+row+"/512/png8?app_id=xWVIueSv6JL0aJ5xqTxb&app_code=djPZyynKsbTjIUDOBcHZ2g&lg=eng&ppi=72&pview=DEF"; return url; } + } export = NokiaTiledLayer; \ No newline at end of file diff --git a/src/world/OsmTiledLayer.ts b/src/world/layers/OsmTiledLayer.ts similarity index 68% rename from src/world/OsmTiledLayer.ts rename to src/world/layers/OsmTiledLayer.ts index 44d9b85..4b46ea1 100644 --- a/src/world/OsmTiledLayer.ts +++ b/src/world/layers/OsmTiledLayer.ts @@ -1,14 +1,16 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class OsmTiledLayer extends TiledLayer { - getImageUrl(level: number, row: number, column: number): string { + + getTileUrl(level: number, row: number, column: number): string { var sum = level + row + column; var idx = sum % 3; var server = ["a", "b", "c"][idx]; var url = "//" + server + ".tile.openstreetmap.org/" + level + "/" + column + "/" + row + ".png"; return url; } + } export = OsmTiledLayer; \ No newline at end of file diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts new file mode 100644 index 0000000..db95d1e --- /dev/null +++ b/src/world/layers/PoiLayer.ts @@ -0,0 +1,25 @@ +/// +import Kernel = require('../Kernel'); +import Utils = require('../Utils'); +import MathUtils = require('../math/Math'); +import GraphicGroup = require('../GraphicGroup'); +import Poi = require('../graphics/Poi'); +import Marker = require('../geometries/Marker'); +import PoiMaterial = require('../materials/PoiMaterial'); +import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); + +class PoiLayer extends GraphicGroup{ + constructor(){ + super(); + + var p = MathUtils.geographicToCartesianCoord(116.408540, 39.902350, Kernel.EARTH_RADIUS + 0.001); + //var p = MathUtils.geographicToCartesianCoord(0, 0, Kernel.EARTH_RADIUS * 1.2); + var marker = new Marker(p.x, p.y, p.z); + var url = "/WebGlobe/src/world/images/poi.png"; + var material = new PoiMaterial(url, 24); + var poi = new Poi(marker, material); + this.add(poi); + } +} + +export = PoiLayer; \ No newline at end of file diff --git a/src/world/layers/SosoTiledLayer.ts b/src/world/layers/SosoTiledLayer.ts new file mode 100644 index 0000000..4c508d3 --- /dev/null +++ b/src/world/layers/SosoTiledLayer.ts @@ -0,0 +1,57 @@ +/// +import TiledLayer = require('./TiledLayer'); + +class SosoTiledLayer extends TiledLayer { + + getTileUrl(level: number, row: number, column: number): string { + if(level >= 10){ + return this._getPoliticalUrl(level, row, column); + } + + //return this._getDemUrl(level, row, column); + return this._getImageUrl(level, row, column); + } + + //地形图 + private _getDemUrl(level: number, row: number, column: number): string{ + //http://p0.map.gtimg.com/demTiles/4/0/0/11_9.jpg + var tileCount = Math.pow(2, level); + var a = column; + var b = tileCount - row - 1; + var A = Math.floor(a / 16); + var B = Math.floor(b / 16); + var sum = level + row + column; + var serverIdx = sum % 4; //0、1、2、3 + var url = `//p${serverIdx}.map.gtimg.com/demTiles/${level}/${A}/${B}/${a}_${b}.jpg`; + return url; + } + + + //影像图 + private _getImageUrl(level: number, row: number, column: number): string { + //http://p2.map.gtimg.com/sateTiles/8/12/9/201_157.jpg?version=101 + //var maptileUrl = "http://p"+serverIdx+".map.soso.com/maptilesv2/"+level+"/"+A+"/"+B+"/"+a+"_"+b+".png"; + var tileCount = Math.pow(2, level); + var a = column; + var b = tileCount - row - 1; + var A = Math.floor(a / 16); + var B = Math.floor(b / 16); + var sum = level + row + column; + var serverIdx = sum % 4; //0、1、2、3 + var url = `//p${serverIdx}.map.gtimg.com/sateTiles/${level}/${A}/${B}/${a}_${b}.jpg?version=101`; + return url; + } + + //行政区划图 + private _getPoliticalUrl(level: number, row: number, column: number): string { + //["http://rt0.map.gtimg.com/tile", "http://rt1.map.gtimg.com/tile", "http://rt2.map.gtimg.com/tile", "http://rt3.map.gtimg.com/tile"] + row = Math.pow(2, level) - row - 1; + var index:number = (level + row + column) % 4; + //http://rt2.map.gtimg.com/tile?z=4&x=11&y=9&type=vector&styleid=3&version=112 + var url = `//rt${index}.map.gtimg.com/tile?z=${level}&x=${column}&y=${row}&type=vector&styleid=3&version=112`; + //need proxy + return this.wrapUrlWithProxy(url); + } +} + +export = SosoTiledLayer; \ No newline at end of file diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts new file mode 100644 index 0000000..7a5f069 --- /dev/null +++ b/src/world/layers/SubTiledLayer.ts @@ -0,0 +1,118 @@ +/// +import Kernel = require('../Kernel'); +import Utils = require('../Utils'); +import MathUtils = require('../math/Math'); +import TileGrid = require('../TileGrid'); +import GraphicGroup = require('../GraphicGroup'); +import Tile = require('../graphics/Tile'); +import TiledLayer = require('./TiledLayer'); + +class SubTiledLayer extends GraphicGroup { + tiledLayer: TiledLayer = null; + + constructor(public level: number) { + super(); + } + + //重写GraphicGroup的add方法 + add(tile: Tile) { + if (tile.tileInfo.level === this.level) { + super.add(tile); + tile.subTiledLayer = this; + } + } + + //重写GraphicGroup的destroy方法 + destroy() { + super.destroy(); + this.tiledLayer = null; + } + + //根据level、row、column查找tile,可以供调试用 + findTile(level: number, row: number, column: number) { + var length = this.children.length; + for (var i = 0; i < length; i++) { + var tile = this.children[i]; + if (tile.tileInfo.level === level && tile.tileInfo.row === row && tile.tileInfo.column === column) { + return tile; + } + } + return null; + } + + //根据传入的tiles信息进行更新其children + updateTiles(visibleTileGrids: TileGrid[], bAddNew: boolean) { + //检查visibleTileGrids中是否存在指定的切片信息 + function checkTileExist(tileArray: TileGrid[], lev: number, row: number, col: number): any { + var result = { + isExist: false, + index: -1 + }; + for (var m = 0; m < tileArray.length; m++) { + var tileInfo = tileArray[m]; + if (tileInfo.level === lev && tileInfo.row === row && tileInfo.column === col) { + result.isExist = true; + result.index = m; + return result; + } + } + return result; + } + + //记录应该删除的切片 + var tilesNeedDelete: Tile[] = []; + var i:number, tile:Tile; + for (i = 0; i < this.children.length; i++) { + tile = this.children[i]; + var checkResult = checkTileExist(visibleTileGrids, tile.tileInfo.level, tile.tileInfo.row, tile.tileInfo.column); + var isExist = checkResult.isExist; + if (isExist) { + visibleTileGrids.splice(checkResult.index, 1); //已处理 + } else { + //暂时不删除,先添加要删除的标记,循环删除容易出错 + tilesNeedDelete.push(tile); + } + } + + //集中进行删除 + while (tilesNeedDelete.length > 0) { + var b = this.remove(tilesNeedDelete[0]); + tilesNeedDelete.splice(0, 1); + if (!b) { + console.debug("subTiledLayer.remove(tilesNeedDelete[0])失败"); + } + } + + if (bAddNew) { + //添加新增的切片 + console.log(`level: ${this.level}, new added count: ${visibleTileGrids.length}`); + for (i = 0; i < visibleTileGrids.length; i++) { + var tileGridInfo = visibleTileGrids[i]; + var args = { + level: tileGridInfo.level, + row: tileGridInfo.row, + column: tileGridInfo.column, + url: "" + }; + args.url = this.tiledLayer.getTileUrl(args.level, args.row, args.column); + tile = Tile.getInstance(args.level, args.row, args.column, args.url); + this.add(tile); + } + } + } + + checkIfLoaded() { + for (var i = 0; i < this.children.length; i++) { + var tile = this.children[i]; + if (tile) { + var isTileLoaded = tile.material.isReady(); + if (!isTileLoaded) { + return false; + } + } + } + return true; + } +} + +export = SubTiledLayer; \ No newline at end of file diff --git a/src/world/TiandituTiledLayer.ts b/src/world/layers/TiandituTiledLayer.ts similarity index 66% rename from src/world/TiandituTiledLayer.ts rename to src/world/layers/TiandituTiledLayer.ts index 65f6f94..458d7db 100644 --- a/src/world/TiandituTiledLayer.ts +++ b/src/world/layers/TiandituTiledLayer.ts @@ -1,14 +1,16 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class TiandituTiledLayer extends TiledLayer { - getImageUrl(level: number, row: number, column: number): string { + + getTileUrl(level: number, row: number, column: number): string { var url = ""; var sum = level + row + column; var serverIdx = sum % 8; url = "//t" + serverIdx + ".tianditu.com/DataServer?T=vec_w&x=" + column + "&y=" + row + "&l=" + level; return url; } + } export = TiandituTiledLayer; \ No newline at end of file diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts new file mode 100644 index 0000000..ac3b44e --- /dev/null +++ b/src/world/layers/TiledLayer.ts @@ -0,0 +1,115 @@ +/// +import Kernel = require('../Kernel'); +import GraphicGroup = require('../GraphicGroup'); +import SubTiledLayer = require('./SubTiledLayer'); +import Camera from '../Camera'; +import Tile = require("../graphics/Tile"); +import TileGrid = require('../TileGrid'); +import Utils = require('../Utils'); + +abstract class TiledLayer extends GraphicGroup { + + constructor(){ + super(); + + //添加第0级的子图层 + var subLayer0 = new SubTiledLayer(0); + this.add(subLayer0); + + //要对level为1的图层进行特殊处理,在创建level为1时就创建其中的全部的四个tile + var subLayer1 = new SubTiledLayer(1); + this.add(subLayer1); + + for (var m = 0; m <= 1; m++) { + for (var n = 0; n <= 1; n++) { + var args = { + level: 1, + row: m, + column: n, + url: "" + }; + args.url = this.getTileUrl(args.level, args.row, args.column); + var tile = Tile.getInstance(args.level, args.row, args.column, args.url); + subLayer1.add(tile); + } + } + } + + refresh(lastLevel: number, lastLevelTileGrids: TileGrid[]){ + this._updateSubLayerCount(lastLevel); + + var levelsTileGrids: TileGrid[][] = []; + var parentTileGrids = lastLevelTileGrids; + var subLevel: number; + + for (subLevel = lastLevel; subLevel >= 2; subLevel--) { + levelsTileGrids[subLevel] = parentTileGrids;//此行代码表示第subLevel层级的可见切片 + parentTileGrids = Utils.map(parentTileGrids, function (item) { + return item.getParent(); + }); + parentTileGrids = Utils.filterRepeatArray(parentTileGrids); + } + + console.log("----------------------------------------------------------"); + + for (subLevel = 2; subLevel <= lastLevel; subLevel++) { + var addNew = lastLevel === subLevel || (lastLevel - subLevel) > 2; + (this.children[subLevel]).updateTiles(levelsTileGrids[subLevel], addNew); + } + } + + //重写 + draw(camera: Camera){ + var gl = Kernel.gl; + //此处将深度测试设置为ALWAYS是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 + gl.depthFunc(gl.ALWAYS); + super.draw(camera); + //将深度测试恢复成LEQUAL + gl.depthFunc(gl.LEQUAL); + } + + //重写 + add(subTiledLayer: SubTiledLayer) { + super.add(subTiledLayer); + subTiledLayer.tiledLayer = this; + } + + protected wrapUrlWithProxy(url: string): string{ + if(Kernel.proxy){ + return Kernel.proxy + "?" + url; + } + return url; + } + + //根据切片的层级以及行列号获取图片的url,抽象方法,供子类实现 + abstract getTileUrl(level: number, row: number, column: number): string + + //根据传入的level更新SubTiledLayer的数量 + private _updateSubLayerCount(level: number) { + var subLayerCount = this.children.length; + var deltaLevel = level + 1 - subLayerCount; + var i: number, subLayer: SubTiledLayer; + if (deltaLevel > 0) { + //需要增加子图层 + for (i = 0; i < deltaLevel; i++) { + subLayer = new SubTiledLayer(i + subLayerCount); + this.add(subLayer); + } + } else if (deltaLevel < 0) { + //需要删除多余的子图层 + deltaLevel *= -1; + for (i = 0; i < deltaLevel; i++) { + var removeLevel = this.children.length - 1; + //第0级和第1级不删除 + if (removeLevel >= 2) { + subLayer = this.children[removeLevel]; + this.remove(subLayer); + } else { + break; + } + } + } + } +} + +export = TiledLayer; \ No newline at end of file diff --git a/src/world/materials/Material.ts b/src/world/materials/Material.ts new file mode 100644 index 0000000..103d922 --- /dev/null +++ b/src/world/materials/Material.ts @@ -0,0 +1,8 @@ +/// +abstract class Material{ + abstract isReady(): boolean + abstract getType(): string + abstract destroy(): void +} + +export = Material; \ No newline at end of file diff --git a/src/world/materials/MeshColorMaterial.ts b/src/world/materials/MeshColorMaterial.ts new file mode 100644 index 0000000..671b97c --- /dev/null +++ b/src/world/materials/MeshColorMaterial.ts @@ -0,0 +1,55 @@ +/// +import Material = require("./Material"); + +class MeshColorMaterial extends Material { + type: string = ""; + ready: boolean = false; + singleColor: number[]; + triangleColors: number[][]; + verticeColors: number[][]; + + constructor() { + super(); + this.reset(); + } + + isReady(){ + return this.ready; + } + + getType(){ + return "MeshColorMaterial"; + } + + reset() { + this.type = ''; + this.singleColor = null; + this.triangleColors = []; + this.verticeColors = []; + this.ready = false; + } + + setSingleColor(color: number[]) { + this.type = 'single'; + this.singleColor = color; + this.ready = true; + } + + setTriangleColor(colors: number[][]) { + this.type = 'triangle'; + this.triangleColors = colors; + this.ready = true; + }; + + setVerticeColor(colors: number[][]) { + this.type = 'vertice'; + this.verticeColors = colors; + this.ready = true; + } + + destroy() { + this.reset(); + } +} + +export = MeshColorMaterial; \ No newline at end of file diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts new file mode 100644 index 0000000..33d53c8 --- /dev/null +++ b/src/world/materials/MeshTextureMaterial.ts @@ -0,0 +1,123 @@ +/// +import Kernel = require("../Kernel"); +import MathUtils = require("../math/Math"); +import Material = require("./Material"); +import ImageUtils = require('../Image'); + +type ImageType = HTMLImageElement | string; + +class MeshTextureMaterial extends Material { + texture: WebGLTexture; + image: HTMLImageElement; + private ready:boolean = false; + private deleted: boolean = false; + + constructor(imageOrUrl: ImageType = null, public flipY: boolean = false) { + super(); + this.texture = Kernel.gl.createTexture(); + if(imageOrUrl){ + this.setImageOrUrl(imageOrUrl); + } + } + + getType(){ + return "MeshTextureMaterial"; + } + + isReady(): boolean{ + return this.ready && !this.deleted; + } + + setImageOrUrl(imageOrUrl?: ImageType){ + if(!imageOrUrl){ + return; + } + if (imageOrUrl instanceof Image && imageOrUrl.width > 0 && imageOrUrl.height > 0) { + this.setImage(imageOrUrl); + } else if (typeof imageOrUrl === "string") { + this.setImageUrl(imageOrUrl); + } + } + + setImage(image: HTMLImageElement) { + if (image.width > 0 && image.height > 0) { + this.ready = false; + this.image = image; + this.onLoad(); + } + } + + setImageUrl(url: string) { + var tileImage = ImageUtils.get(url); + if(tileImage){ + this.setImage(tileImage); + }else{ + this.ready = false; + this.image = new Image(); + //很重要,因为图片是跨域获得的,所以一定要加上此句代码 + this.image.crossOrigin = 'anonymous'; + this.image.onload = this.onLoad.bind(this); + this.image.src = url; + } + } + + //图片加载完成时触发 + protected onLoad() { + //要考虑纹理已经被移除掉了图片才进入onLoad这种情况 + if (this.deleted) { + return; + } + + var gl = Kernel.gl; + + gl.bindTexture(gl.TEXTURE_2D, this.texture); + + if(this.flipY){ + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, +true); + }else{ + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, +false); + } + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.image); + + var isMipMap = this.image.width === this.image.height && MathUtils.isPowerOfTwo(this.image.width); + + if (isMipMap) { + //使用MipMap + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); //LINEAR_MIPMAP_NEAREST LINEAR_MIPMAP_LINEAR + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.generateMipmap(gl.TEXTURE_2D); + } else { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);//gl.NEAREST + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);//gl.NEAREST + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + } + + gl.bindTexture(gl.TEXTURE_2D, null); + + this.ready = true; + } + + //释放显卡中的texture资源 + destroy() { + var gl = Kernel.gl; + // if (gl.isTexture(this.texture)) { + // gl.deleteTexture(this.texture); + // } + if(this.texture){ + gl.deleteTexture(this.texture); + } + if(this.image && !this.ready){ + console.log(`Cancel load image ${this.image.src}`); + this.image.src = ""; + } + this.ready = false; + this.texture = null; + this.deleted = true; + } +} + +export = MeshTextureMaterial; \ No newline at end of file diff --git a/src/world/materials/PoiMaterial.ts b/src/world/materials/PoiMaterial.ts new file mode 100644 index 0000000..aed4be3 --- /dev/null +++ b/src/world/materials/PoiMaterial.ts @@ -0,0 +1,18 @@ +/// + +import MeshTextureMaterial = require("./MeshTextureMaterial"); + +type ImageType = string | HTMLImageElement; + +class PoiMaterial extends MeshTextureMaterial{ + + constructor(imageOrUrl?: ImageType, public size:number = 16){ + super(imageOrUrl, false); + } + + getType(){ + return "PoiMaterial"; + } +} + +export = PoiMaterial; \ No newline at end of file diff --git a/src/world/materials/TileMaterial.ts b/src/world/materials/TileMaterial.ts new file mode 100644 index 0000000..0d2d143 --- /dev/null +++ b/src/world/materials/TileMaterial.ts @@ -0,0 +1,23 @@ +/// +import MeshTextureMaterial= require('./MeshTextureMaterial'); +import ImageUtils = require('../Image'); + +type ImageType = HTMLImageElement | string; + +class TileMaterial extends MeshTextureMaterial{ + level: number; + + constructor(level: number, imageOrUrl: ImageType){ + super(imageOrUrl, true); + this.level = level >= 0 ? level : 20; + } + + onLoad() { + if (this.level <= ImageUtils.MAX_LEVEL) { + ImageUtils.add(this.image.src, this.image); + } + super.onLoad(); + } +} + +export = TileMaterial; \ No newline at end of file diff --git a/src/world/Line.ts b/src/world/math/Line.ts similarity index 92% rename from src/world/Line.ts rename to src/world/math/Line.ts index 5862957..d40e763 100644 --- a/src/world/Line.ts +++ b/src/world/math/Line.ts @@ -1,4 +1,4 @@ -/// +/// import Vertice = require('./Vertice'); import Vector = require('./Vector'); @@ -16,7 +16,7 @@ class Line{ this.vertice = position.clone(); return this; } - + setVector(direction: Vector): Line { this.vector = direction.clone(); this.vector.normalize(); diff --git a/src/world/Math.ts b/src/world/math/Math.ts similarity index 87% rename from src/world/Math.ts rename to src/world/math/Math.ts index f15c81f..462a5be 100644 --- a/src/world/Math.ts +++ b/src/world/math/Math.ts @@ -1,6 +1,6 @@ -/// -import Kernel = require('./Kernel'); -import Utils = require('./Utils'); +/// +import Kernel = require('../Kernel'); +import Utils = require('../Utils'); import Vertice = require('./Vertice'); import Vector = require('./Vector'); import Line = require('./Line'); @@ -25,6 +25,30 @@ const MathUtils = { return Math.abs(value) < 0.000001; }, + isPowerOfTwo(value: number) { + return ( value & ( value - 1 ) ) === 0 && value !== 0; + }, + + asinSafely(value: number){ + if(value > 1){ + value = 1; + } + if(value < -1){ + value = -1; + } + return Math.asin(value); + }, + + acosSafely(value: number){ + if(value > 1){ + value = 1; + } + if(value < -1){ + value = -1; + } + return Math.acos(value); + }, + /** * 将其他进制的数字转换为10进制 * @param numSys 要准换的进制 @@ -305,7 +329,7 @@ const MathUtils = { } var ndcX = 2 * canvasX / Kernel.canvas.width - 1; var ndcY = 1 - 2 * canvasY / Kernel.canvas.height; - return [ndcX,ndcY]; + return [ndcX, ndcY]; }, //点变换: NDC->Canvas @@ -318,16 +342,7 @@ const MathUtils = { } var canvasX = (1 + ndcX) * Kernel.canvas.width / 2.0; var canvasY = (1 - ndcY) * Kernel.canvas.height / 2.0; - return [canvasX,canvasY]; - }, - - /** - * 根据层级计算出摄像机应该放置到距离地球表面多远的位置 - * @param level - * @return {*} - */ - getLengthFromCamera2EarthSurface(level: number): number{ - return 7820683/Math.pow(2,level); + return [canvasX, canvasY]; }, /**将经纬度转换为笛卡尔空间直角坐标系中的x、y、z @@ -337,10 +352,10 @@ const MathUtils = { * @p 笛卡尔坐标系中的坐标 */ geographicToCartesianCoord(lon:number, lat: number, r: number = Kernel.EARTH_RADIUS): Vertice{ - if(!(lon >= -(180+0.001) && lon <= (180+0.001))){ + if(!(lon >= -(180 + 0.001) && lon <= (180 + 0.001))){ throw "invalid lon"; } - if(!(lat >= -(90+0.001) && lat <= (90+0.001))){ + if(!(lat >= -(90 + 0.001) && lat <= (90 + 0.001))){ throw "invalid lat"; } var radianLon = this.degreeToRadian(lon); @@ -349,10 +364,10 @@ const MathUtils = { var cos1 = Math.cos(radianLon); var sin2 = Math.sin(radianLat); var cos2 = Math.cos(radianLat); - var x = r*sin1*cos2; - var y = r*sin2; - var z = r*cos1*cos2; - return new Vertice(x,y,z); + var x = r * sin1 *cos2; + var y = r * sin2; + var z = r *cos1 * cos2; + return new Vertice(x, y, z); }, /** @@ -365,43 +380,40 @@ const MathUtils = { var x = verticeCopy.x; var y = verticeCopy.y; var z = verticeCopy.z; - var sin2 = y/Kernel.EARTH_RADIUS; - if(sin2 > 1){ - sin2 = 2; - } - else if(sin2 < -1){ - sin2 = -1; - } - var radianLat = Math.asin(sin2); + var sin2 = y / Kernel.EARTH_RADIUS; + var radianLat = this.asinSafely(sin2); var cos2 = Math.cos(radianLat); var sin1 = x / (Kernel.EARTH_RADIUS * cos2); if(sin1 > 1){ sin1 = 1; } - else if(sin1 < -1){ + if(sin1 < -1){ sin1 = -1; } var cos1 = z / (Kernel.EARTH_RADIUS * cos2); if(cos1 > 1){ cos1 = 1; } - else if(cos1 < -1){ + if(cos1 < -1){ cos1 = -1; } - var radianLog = Math.asin(sin1); - if(sin1 >= 0){//经度在[0,π] - if(cos1 >= 0){//经度在[0,π/2]之间 + var radianLog = this.asinSafely(sin1); + if(sin1 >= 0){ + //经度在[0,π] + if(cos1 >= 0){ + //经度在[0, π/2]之间 radianLog = radianLog; - } - else{//经度在[π/2,π]之间 + }else{ + //经度在[π/2, π]之间 radianLog = Math.PI - radianLog; } - } - else{//经度在[-π,0]之间 - if(cos1 >= 0){//经度在[-π/2,0]之间 + }else{ + //经度在[-π, 0]之间 + if(cos1 >= 0){ + //经度在[-π/2, 0]之间 radianLog = radianLog; - } - else{//经度在[-π,-π/2]之间 + }else{ + //经度在[-π,-π/2]之间 radianLog = -radianLog - Math.PI; } } @@ -457,9 +469,9 @@ const MathUtils = { throw "invalid y"; } var a = y / Kernel.EARTH_RADIUS; - var b = Math.pow(Math.E,a); + var b = Math.pow(Math.E, a); var c = Math.atan(b); - var radianLat = 2*c - Math.PI/2; + var radianLat = 2 * c - Math.PI/2; return radianLat; }, @@ -503,7 +515,7 @@ const MathUtils = { * @return {*} 投影坐标x */ radianLogToWebMercatorX(radianLog: number): number{ - if(!(Utils.isNumber(radianLog) && radianLog <= (Math.PI+0.001) && radianLog >= -(Math.PI+0.001))){ + if(!(Utils.isNumber(radianLog) && radianLog <= (Math.PI + 0.001) && radianLog >= -(Math.PI + 0.001))){ throw "invalid radianLog"; } return Kernel.EARTH_RADIUS * radianLog; @@ -515,7 +527,7 @@ const MathUtils = { * @return {*} 投影坐标x */ degreeLogToWebMercatorX(degreeLog: number): number{ - if(!(Utils.isNumber(degreeLog) && degreeLog <= (180+0.001) && degreeLog >= -(180+0.001))){ + if(!(Utils.isNumber(degreeLog) && degreeLog <= (180 + 0.001) && degreeLog >= -(180 + 0.001))){ throw "invalid degreeLog"; } var radianLog = this.degreeToRadian(degreeLog); @@ -528,10 +540,10 @@ const MathUtils = { * @return {Number} 投影坐标y */ radianLatToWebMercatorY(radianLat: number): number{ - if(!(radianLat <= (Math.PI/2+0.001) && radianLat >= -(Math.PI/2+0.001))){ + if(!(radianLat <= (Math.PI / 2 + 0.001) && radianLat >= -(Math.PI / 2 + 0.001))){ throw "invalid radianLat"; } - var a = Math.PI/4 + radianLat/2; + var a = Math.PI / 4 + radianLat / 2; var b = Math.tan(a); var c = Math.log(b); var y = Kernel.EARTH_RADIUS * c; @@ -544,7 +556,7 @@ const MathUtils = { * @return {Number} 投影坐标y */ degreeLatToWebMercatorY(degreeLat: number): number{ - if(!(degreeLat <= (90+0.001) && degreeLat >= -(90+0.001))){ + if(!(degreeLat <= (90 + 0.001) && degreeLat >= -(90 + 0.001))){ throw "invalid degreeLat"; } var radianLat = this.degreeToRadian(degreeLat); @@ -560,7 +572,7 @@ const MathUtils = { radianGeographicToWebMercator(radianLog: number, radianLat: number): number[]{ var x = this.radianLogToWebMercatorX(radianLog); var y = this.radianLatToWebMercatorY(radianLat); - return [x,y]; + return [x, y]; }, /** @@ -572,13 +584,13 @@ const MathUtils = { degreeGeographicToWebMercator(degreeLog: number, degreeLat: number): number[]{ var x = this.degreeLogToWebMercatorX(degreeLog); var y = this.degreeLatToWebMercatorY(degreeLat); - return [x,y]; + return [x, y]; }, //根据切片的level、row、column计算该切片所覆盖的投影区域的范围 getTileWebMercatorEnvelopeByGrid(level: number, row: number, column: number): any{ var k = Kernel.MAX_PROJECTED_COORD; - var size = 2*k / Math.pow(2,level); + var size = 2 * k / Math.pow(2, level); var minX = -k + column * size; var maxX = minX + size; var maxY = k - row * size; @@ -594,14 +606,14 @@ const MathUtils = { //根据切片的level、row、column计算该切片所覆盖的经纬度区域的范围,以经纬度表示返回结果 getTileGeographicEnvelopByGrid(level: number, row: number, column: number): any{ - var Eproj = this.getTileWebMercatorEnvelopeByGrid(level,row,column); - var pMin = this.webMercatorToDegreeGeographic(Eproj.minX,Eproj.minY); - var pMax = this.webMercatorToDegreeGeographic(Eproj.maxX,Eproj.maxY); + var Eproj = this.getTileWebMercatorEnvelopeByGrid(level, row, column); + var pMin = this.webMercatorToDegreeGeographic(Eproj.minX, Eproj.minY); + var pMax = this.webMercatorToDegreeGeographic(Eproj.maxX, Eproj.maxY); var Egeo = { - "minLon":pMin[0], - "minLat":pMin[1], - "maxLon":pMax[0], - "maxLat":pMax[1] + "minLon": pMin[0], + "minLat": pMin[1], + "maxLon": pMax[0], + "maxLat": pMax[1] }; return Egeo; }, @@ -613,19 +625,19 @@ const MathUtils = { var minLat = Egeo.minLat; var maxLon = Egeo.maxLon; var maxLat = Egeo.maxLat; - var pLeftBottom = this.geographicToCartesianCoord(minLon,minLat); - var pLeftTop = this.geographicToCartesianCoord(minLon,maxLat); - var pRightTop = this.geographicToCartesianCoord(maxLon,maxLat); - var pRightBottom = this.geographicToCartesianCoord(maxLon,minLat); + var pLeftBottom = this.geographicToCartesianCoord(minLon, minLat); + var pLeftTop = this.geographicToCartesianCoord(minLon, maxLat); + var pRightTop = this.geographicToCartesianCoord(maxLon, maxLat); + var pRightBottom = this.geographicToCartesianCoord(maxLon, minLat); var Ecar = { - "pLeftBottom":pLeftBottom, - "pLeftTop":pLeftTop, - "pRightTop":pRightTop, - "pRightBottom":pRightBottom, - "minLon":minLon, - "minLat":minLat, - "maxLon":maxLon, - "maxLat":maxLat + "pLeftBottom": pLeftBottom, + "pLeftTop": pLeftTop, + "pRightTop": pRightTop, + "pRightBottom": pRightBottom, + "minLon": minLon, + "minLat": minLat, + "maxLon": maxLon, + "maxLat": maxLat }; return Ecar; }, @@ -638,20 +650,20 @@ const MathUtils = { * @return {Array} */ getGeographicTileCenter(level: number, row: number, column: number): number[]{ - var Egeo = this.getTileGeographicEnvelopByGrid(level,row,column); + var Egeo = this.getTileGeographicEnvelopByGrid(level, row, column); var minLon = Egeo.minLon; var minLat = Egeo.minLat; var maxLon = Egeo.maxLon; var maxLat = Egeo.maxLat; var centerLon = (minLon+maxLon)/2;//切片的经度中心 var centerLat = (minLat+maxLat)/2;//切片的纬度中心 - var lonlatTileCenter = [centerLon,centerLat]; + var lonlatTileCenter = [centerLon, centerLat]; return lonlatTileCenter; }, getCartesianTileCenter(level: number, row: number, column: number): Vertice{ - var lonLat = this.getGeographicTileCenter(level,row,column); - var vertice = this.geographicToCartesianCoord(lonLat[0],lonLat[1]); + var lonLat = this.getGeographicTileCenter(level, row, column); + var vertice = this.geographicToCartesianCoord(lonLat[0], lonLat[1]); return vertice; }, @@ -662,15 +674,15 @@ const MathUtils = { * @return {Array} 返回每个顶点的平均法向量的数组 */ calculateNormals(vs: number[], ind: number[]): number[]{ - var x=0; - var y=1; - var z=2; + var x = 0; + var y = 1; + var z = 2; var ns:number[] = []; //对于每个vertex,初始化normal x, normal y, normal z - for(var i=0;i +/// import Vertice = require('./Vertice'); import Vector = require('./Vector'); @@ -14,6 +14,15 @@ class Matrix{ this.setElements(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); } + equals(matrix: Matrix): boolean{ + if(this === matrix){ + return true; + } + return this.elements.every((ele: number, index: number) => { + return ele === matrix.elements[index]; + }); + } + setElements(m11: number, m12: number, m13: number, m14: number, m21: number, m22: number, m23: number, m24: number, m31: number, m32: number, m33: number, m34: number, @@ -42,43 +51,43 @@ class Matrix{ return this; } - setColumnX(x: number, y: number, z: number) { - this.elements[0] = x; - this.elements[1] = y; - this.elements[2] = z; + setVectorX(vector: Vector) { + this.elements[0] = vector.x; + this.elements[1] = vector.y; + this.elements[2] = vector.z; } - getColumnX(): Vector { + getVectorX(): Vector { return new Vector(this.elements[0], this.elements[1], this.elements[2]); } - setColumnY(x: number, y: number, z: number) { - this.elements[4] = x; - this.elements[5] = y; - this.elements[6] = z; + setVectorY(vector: Vector) { + this.elements[4] = vector.x; + this.elements[5] = vector.y; + this.elements[6] = vector.z; } - getColumnY(): Vector { + getVectorY(): Vector { return new Vector(this.elements[4], this.elements[5], this.elements[6]); } - setColumnZ(x: number, y: number, z: number) { - this.elements[8] = x; - this.elements[9] = y; - this.elements[10] = z; + setVectorZ(vector: Vector) { + this.elements[8] = vector.x; + this.elements[9] = vector.y; + this.elements[10] = vector.z; } - getColumnZ(): Vector { + getVectorZ(): Vector { return new Vector(this.elements[8], this.elements[9], this.elements[10]); } - setColumnTrans(x: number, y: number, z: number) { - this.elements[12] = x; - this.elements[13] = y; - this.elements[14] = z; + setPosition(vertice: Vertice) { + this.elements[12] = vertice.x; + this.elements[13] = vertice.y; + this.elements[14] = vertice.z; } - getColumnTrans(): Vertice { + getPosition(): Vertice { return new Vertice(this.elements[12], this.elements[13], this.elements[14]); } @@ -132,21 +141,21 @@ class Matrix{ var result: Matrix = new Matrix(); var b = result.elements; var c = a[0], - d = a[1], - e = a[2], - g = a[3], - f = a[4], - h = a[5], - i = a[6], - j = a[7], - k = a[8], - l = a[9], - n = a[10], - o = a[11], - m = a[12], - p = a[13], - r = a[14], - s = a[15]; + d = a[1], + e = a[2], + g = a[3], + f = a[4], + h = a[5], + i = a[6], + j = a[7], + k = a[8], + l = a[9], + n = a[10], + o = a[11], + m = a[12], + p = a[13], + r = a[14], + s = a[15]; var A = c * h - d * f; var B = c * i - e * f; var t = c * j - g * f; @@ -163,7 +172,7 @@ class Matrix{ if (!q) { console.log("can't get inverse matrix"); return null - }; + } q = 1 / q; b[0] = (h * E - i * D + j * C) * q; b[1] = (-d * E + e * D - g * C) * q; @@ -185,9 +194,6 @@ class Matrix{ } setMatrixByOther(otherMatrix: Matrix): void { - if (!(otherMatrix instanceof Matrix)) { - throw "invalid otherMatrix"; - } for (var i = 0; i < otherMatrix.elements.length; i++) { this.elements[i] = otherMatrix.elements[i]; } @@ -201,7 +207,14 @@ class Matrix{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, - 0, 0, 0, 1); + 0, 0, 0, 1 + ); + } + + setUniqueValue(value: number){ + this.elements.forEach((ele, index) => { + this.elements[index] = value; + }); } /** @@ -231,7 +244,8 @@ class Matrix{ this.elements[0], this.elements[4], this.elements[8], this.elements[12], this.elements[1], this.elements[5], this.elements[9], this.elements[13], this.elements[2], this.elements[6], this.elements[10], this.elements[14], - this.elements[3], this.elements[7], this.elements[11], this.elements[15]); + this.elements[3], this.elements[7], this.elements[11], this.elements[15] + ); } multiplyMatrix(otherMatrix: Matrix): Matrix { @@ -275,6 +289,12 @@ class Matrix{ return [m11, m21, m31, m41]; } + hasNaN():boolean{ + return this.elements.some(function(v){ + return isNaN(v); + }); + } + divide(a: number) { if (a === 0) { throw "invalid a:a is 0"; @@ -286,10 +306,6 @@ class Matrix{ } } - getPosition(): Vertice{ - return this.getColumnTrans(); - } - worldTranslate(x: number, y: number, z: number) { this.elements[12] += x; this.elements[13] += y; @@ -316,10 +332,10 @@ class Matrix{ } localScale(scaleX: number, scaleY: number, scaleZ: number): void { - var transVertice = this.getColumnTrans(); - this.setColumnTrans(0, 0, 0); + var transVertice = this.getPosition(); + this.setPosition(new Vertice(0, 0, 0)); this.worldScale(scaleX, scaleY, scaleZ); - this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); + this.setPosition(transVertice); } worldRotateX(radian: number): void { @@ -412,27 +428,27 @@ class Matrix{ } localRotateX(radian: number): void { - var transVertice = this.getColumnTrans(); - this.setColumnTrans(0, 0, 0); - var columnX = this.getColumnX(); + var transVertice = this.getPosition(); + this.setPosition(new Vertice(0, 0, 0)); + var columnX = this.getVectorX(); this.worldRotateByVector(radian, columnX); - this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); + this.setPosition(transVertice); } localRotateY(radian: number): void { - var transVertice = this.getColumnTrans(); - this.setColumnTrans(0, 0, 0); - var columnY = this.getColumnY(); + var transVertice = this.getPosition(); + this.setPosition(new Vertice(0, 0, 0)); + var columnY = this.getVectorY(); this.worldRotateByVector(radian, columnY); - this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); + this.setPosition(transVertice); } localRotateZ(radian: number): void { - var transVertice = this.getColumnTrans(); - this.setColumnTrans(0, 0, 0); - var columnZ = this.getColumnZ(); + var transVertice = this.getPosition(); + this.setPosition(new Vertice(0, 0, 0)); + var columnZ = this.getVectorZ(); this.worldRotateByVector(radian, columnZ); - this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); + this.setPosition(transVertice); } //localVector指的是相对于模型坐标系中的向量 @@ -442,10 +458,10 @@ class Matrix{ var worldColumn = this.multiplyColumn(localColumn); //模型坐标转换为世界坐标 var worldVector = new Vector(worldColumn[0], worldColumn[1], worldColumn[2]); - var transVertice = this.getColumnTrans(); - this.setColumnTrans(0, 0, 0); + var transVertice = this.getPosition(); + this.setPosition(new Vertice(0, 0, 0)); this.worldRotateByVector(radian, worldVector); - this.setColumnTrans(transVertice.x, transVertice.y, transVertice.z); + this.setPosition(transVertice); } }; diff --git a/src/world/Plan.ts b/src/world/math/Plan.ts similarity index 80% rename from src/world/Plan.ts rename to src/world/math/Plan.ts index 51f6b8c..2aa9369 100644 --- a/src/world/Plan.ts +++ b/src/world/math/Plan.ts @@ -1,4 +1,4 @@ -/// +/// class Plan{ constructor(public A: number, public B: number, public C: number, public D: number){ } diff --git a/src/world/Ray.ts b/src/world/math/Ray.ts similarity index 94% rename from src/world/Ray.ts rename to src/world/math/Ray.ts index e79c12f..3191149 100644 --- a/src/world/Ray.ts +++ b/src/world/math/Ray.ts @@ -1,4 +1,4 @@ -/// +/// import Vertice = require('./Vertice'); import Vector = require('./Vector'); @@ -32,7 +32,7 @@ class Ray{ var rayCopy = new Ray(this.vertice, this.vector); return rayCopy; } - + rotateVertice(vertice: Vertice): Vertice { return null; } diff --git a/src/world/Vector.ts b/src/world/math/Vector.ts similarity index 88% rename from src/world/Vector.ts rename to src/world/math/Vector.ts index 6c366a6..3bae7bf 100644 --- a/src/world/Vector.ts +++ b/src/world/math/Vector.ts @@ -1,4 +1,4 @@ -/// +/// import Vertice = require('./Vertice'); class Vector{ @@ -16,6 +16,21 @@ class Vector{ return new Vertice(vertice.x + vector.x, vertice.y + vector.y, vertice.z + vector.z); } + static getRadianOfTwoVectors(vector1: Vector, vector2: Vector): number{ + var v1 = vector1.clone().normalize(); + var v2 = vector2.clone().normalize(); + var dotValue = v1.dot(v2); + //dotValue的值应该在[-1,1],但是由于JavaScript精度问题,导致计算的值有可能超出该范围,例如1.0000000000000002 + if(dotValue < -1){ + dotValue = -1; + } + if(dotValue > 1){ + dotValue = 1; + } + var radian = Math.acos(dotValue); + return radian; + } + getVertice(): Vertice { return new Vertice(this.x, this.y, this.z); } diff --git a/src/world/math/Vertice.ts b/src/world/math/Vertice.ts new file mode 100644 index 0000000..3c8317c --- /dev/null +++ b/src/world/math/Vertice.ts @@ -0,0 +1,19 @@ +/// + +class Vertice{ + constructor(public x = 0, public y = 0, public z = 0){} + + getArray(): number[] { + return [this.x, this.y, this.z]; + } + + clone(): Vertice { + return new Vertice(this.x, this.y, this.z); + } + + getOpposite(): Vertice { + return new Vertice(-this.x, -this.y, -this.z); + } +} + +export = Vertice; \ No newline at end of file diff --git a/versions.txt b/versions.txt index fa925e1..05b52fe 100644 --- a/versions.txt +++ b/versions.txt @@ -84,4 +84,27 @@ 0.1.0 将Globe.js拆分成多个AMD模块,AMD代码放在js目录下;并将AMD代码用TypeScript重新实现,放到ts目录下,并对原有AMD的代码进行微调:解除了Vertice与Vector之间的相互引用,解除了Math与TileGrid之间的相互引用;通过gulp分别实现了对AMD和TypeScript代码进行编译压缩,通过requirejs均可加载相应bundle代码 -0.1.1 在js和ts中均修复了在两极地区抖动的问题:world/Event#moveGeo() \ No newline at end of file +0.1.1 在js和ts中均修复了在两极地区抖动的问题:world/Event#moveGeo() + +0.2 引入World.js中的许多类的设计理念,包括Program、GraphicGroup、Graphic、Geometry、Material、VertexBufferObject等,现在SubTiledLayer和TiledLayer都继承自GraphicGroup,并且让Tile继承自于Graphic + +0.2.1 修复了在leve 15及以上的情况下,拖动地图导致地图空白的问题,见issue#9 + +0.2.2 删除高程相关代码 + +0.2.3 将world/geometries/Geomtry重命名为world/geometries/Mesh,并在package.json中添加了多个npm scripts,对应gulp中定义的tasks + +0.2.4 之前EARTH_RADIUS的值为6378137,特别大,导致了深度值计算有问题,从而使得深度测试不能正常进行。将EARTH_RADIUS改成14000,使得深度测试可以正常进行。并加入了Poi和PoiLayer这两个类。 + +0.3.0 将EARTH_RADIUS从14000改为500,在0-10级的时候,通过改变Camera的position实现缩放,在10级之后通过改变fov实现缩放。 + Camera中所有的计算(比如计算视野中的可见切片)都是基于matrix、viewMatrix、projMatrix和projViewMatrix。 + 但是实际传递给shader用于绘图的是projViewMatrixForDraw。Camera.getPickCartesianCoordInEarthByCanvas()方法也是基于projViewMatrixForDraw系列矩阵的。 + 该版本提高了深度值的精度,基本解决了z值精度问题。在update()方法中会计算projViewMatrixForDraw系列矩阵。 + +0.3.1 使得Globe.animateToLevel()可以在0.3.0的版本上运行,解决办法是引入了camera.animationLevel,其值是非整数。 + +0.3.2 优化了Camera.update()方法,只有发生用户交互的情况下才实际进行计算。 + +0.3.3 优化了Globe.refresh()方法,只有发生用户交互的情况下才重新计算可见切片。 + +0.3.4 增加了Atmosphere效果。 \ No newline at end of file