From cac595f9c6e555d8be9017c3a513ac47bb4e24aa Mon Sep 17 00:00:00 2001 From: Sun Qun <1039219852@qq.com> Date: Thu, 20 Oct 2016 12:17:10 +0800 Subject: [PATCH 001/109] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae4c0f7..b0ef2f8 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ 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/)作为编辑器。 @@ -29,7 +29,7 @@ Demo: https://ispring.github.io/WebGlobe/index.html - clear用于清除编译打包的结果 - compile用于将TypeScript版本的模块编译成JavaScript版本的AMD模块 - bundle用于将TypeScript版本的模块打包成一个JavaScript压缩文件 - - build用于执行以上所有的task + - build用于执行以上所有的task,且是默认的task 6. 通过index-src.html可以加载AMD格式的源码,方便调试;通过index-bundle.html可以加载打打包压缩后的JavaScript文件,减少了文件体积和网络请求数量,用于生产环境。 From 6d06931995f4f580ba1ea208f977ba5847f42a12 Mon Sep 17 00:00:00 2001 From: Sun Qun <1039219852@qq.com> Date: Thu, 20 Oct 2016 20:46:36 +0800 Subject: [PATCH 002/109] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0ef2f8..36d7513 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Demo: https://ispring.github.io/WebGlobe/index.html - clear用于清除编译打包的结果 - compile用于将TypeScript版本的模块编译成JavaScript版本的AMD模块 - bundle用于将TypeScript版本的模块打包成一个JavaScript压缩文件 - - build用于执行以上所有的task,且是默认的task +    - build用于执行以上所有的task,且是默认的task 6. 通过index-src.html可以加载AMD格式的源码,方便调试;通过index-bundle.html可以加载打打包压缩后的JavaScript文件,减少了文件体积和网络请求数量,用于生产环境。 From c50c5e3304b08003acad6761af9106ca61bb28ae Mon Sep 17 00:00:00 2001 From: Sun Qun <1039219852@qq.com> Date: Thu, 20 Oct 2016 20:47:54 +0800 Subject: [PATCH 003/109] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36d7513..92dac3f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Demo: https://ispring.github.io/WebGlobe/index.html - clear用于清除编译打包的结果 - compile用于将TypeScript版本的模块编译成JavaScript版本的AMD模块 - bundle用于将TypeScript版本的模块打包成一个JavaScript压缩文件 -    - build用于执行以上所有的task,且是默认的task + - build用于执行以上所有的task,且是默认的task 6. 通过index-src.html可以加载AMD格式的源码,方便调试;通过index-bundle.html可以加载打打包压缩后的JavaScript文件,减少了文件体积和网络请求数量,用于生产环境。 From 1b1432d50351c9715008dabe8a739ba92fe162b4 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 21 Oct 2016 12:44:25 +0800 Subject: [PATCH 004/109] add folder math and layers --- main.js | 5 +-- src/world/Elevation.ts | 2 +- src/world/Event.ts | 8 ++--- src/world/Globe.ts | 10 +++--- src/world/Object3D.ts | 6 ++-- src/world/Object3DComponents.ts | 4 +-- src/world/PerspectiveCamera.ts | 16 ++++----- src/world/Scene.ts | 2 +- src/world/Tile.ts | 2 +- src/world/TileGrid.ts | 2 +- src/world/Vertice.ts | 37 -------------------- src/world/{ => layers}/ArcGISTiledLayer.ts | 4 +-- src/world/{ => layers}/AutonaviTiledLayer.ts | 4 +-- src/world/{ => layers}/BingTiledLayer.ts | 4 +-- src/world/{ => layers}/BlendTiledLayer.ts | 2 +- src/world/{ => layers}/GoogleTiledLayer.ts | 2 +- src/world/{ => layers}/NokiaTiledLayer.ts | 2 +- src/world/{ => layers}/OsmTiledLayer.ts | 2 +- src/world/{ => layers}/SosoTiledLayer.ts | 2 +- src/world/{ => layers}/SubTiledLayer.ts | 16 ++++----- src/world/{ => layers}/TiandituTiledLayer.ts | 2 +- src/world/{ => layers}/TiledLayer.ts | 6 ++-- src/world/{ => math}/Line.ts | 4 +-- src/world/{ => math}/Math.ts | 6 ++-- src/world/{ => math}/Matrix.ts | 2 +- src/world/{ => math}/Plan.ts | 2 +- src/world/{ => math}/Ray.ts | 4 +-- src/world/{ => math}/Vector.ts | 2 +- src/world/math/Vertice.ts | 19 ++++++++++ 29 files changed, 81 insertions(+), 98 deletions(-) delete mode 100644 src/world/Vertice.ts rename src/world/{ => layers}/ArcGISTiledLayer.ts (74%) rename src/world/{ => layers}/AutonaviTiledLayer.ts (78%) rename src/world/{ => layers}/BingTiledLayer.ts (91%) rename src/world/{ => layers}/BlendTiledLayer.ts (87%) rename src/world/{ => layers}/GoogleTiledLayer.ts (82%) rename src/world/{ => layers}/NokiaTiledLayer.ts (89%) rename src/world/{ => layers}/OsmTiledLayer.ts (83%) rename src/world/{ => layers}/SosoTiledLayer.ts (90%) rename src/world/{ => layers}/SubTiledLayer.ts (93%) rename src/world/{ => layers}/TiandituTiledLayer.ts (81%) rename src/world/{ => layers}/TiledLayer.ts (89%) rename src/world/{ => math}/Line.ts (92%) rename src/world/{ => math}/Math.ts (99%) rename src/world/{ => math}/Matrix.ts (99%) rename src/world/{ => math}/Plan.ts (80%) rename src/world/{ => math}/Ray.ts (94%) rename src/world/{ => math}/Vector.ts (98%) create mode 100644 src/world/math/Vertice.ts diff --git a/main.js b/main.js index 71ad393..156c5d5 100644 --- a/main.js +++ b/main.js @@ -1,5 +1,6 @@ window.onload = function() { - require(["world/Globe", "world/BingTiledLayer", "world/NokiaTiledLayer", "world/OsmTiledLayer", "world/SosoTiledLayer", "world/TiandituTiledLayer", "world/GoogleTiledLayer"], + require(["world/Globe", "world/layers/BingTiledLayer", "world/layers/NokiaTiledLayer", "world/layers/OsmTiledLayer", + "world/layers/SosoTiledLayer", "world/layers/TiandituTiledLayer", "world/layers/GoogleTiledLayer"], function(Globe, BingTiledLayer, NokiaTiledLayer, OsmTiledLayer, SosoTiledLayer, TiandituTiledLayer, GoogleTiledLayer) { function startWebGL() { @@ -41,7 +42,7 @@ } } - + startWebGL(); }); }; \ No newline at end of file diff --git a/src/world/Elevation.ts b/src/world/Elevation.ts index eae4745..2756bf1 100644 --- a/src/world/Elevation.ts +++ b/src/world/Elevation.ts @@ -1,7 +1,7 @@ /// import Kernel = require('./Kernel'); import Utils = require('./Utils'); -import MathUtils = require('./Math'); +import MathUtils = require('./math/Math'); import TileGrid = require('./TileGrid'); const Elevation = { diff --git a/src/world/Event.ts b/src/world/Event.ts index 4a302b1..f336fab 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -1,7 +1,7 @@ /// import Kernel = require("./Kernel"); -import MathUtils = require("./Math"); -import Vector = require("./Vector"); +import MathUtils = require("./math/Math"); +import Vector = require("./math/Vector"); import PerspectiveCamera = require("./PerspectiveCamera"); type MouseMoveListener = (e: MouseEvent) => {}; @@ -96,7 +96,7 @@ const EventModule = { 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); @@ -158,7 +158,7 @@ const EventModule = { if(newLevel >= 0){ //globe.setLevel(newLevel); globe.animateToLevel(newLevel); - } + } }, onKeyDown(event: KeyboardEvent) { diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 7d98204..3e4488a 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -5,8 +5,8 @@ import ShaderContent = require("./ShaderContent"); import Renderer = require("./Renderer"); import PerspectiveCamera = require("./PerspectiveCamera"); import Scene = require("./Scene"); -import TiledLayer = require("./TiledLayer"); -import SubTiledLayer = require("./SubTiledLayer"); +import TiledLayer = require("./layers/TiledLayer"); +import SubTiledLayer = require("./layers/SubTiledLayer"); import Tile = require("./Tile"); import ImageUtils = require("./Image"); import EventUtils = require("./Event"); @@ -84,12 +84,12 @@ class Globe { 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.camera.setLevel(level); this.refresh(); } } @@ -102,7 +102,7 @@ class Globe { animateToLevel(level: number){ if(!this.isAnimating()){ this.camera.animateToLevel(level); - } + } } /** diff --git a/src/world/Object3D.ts b/src/world/Object3D.ts index 1d02bd8..b7ff2c1 100644 --- a/src/world/Object3D.ts +++ b/src/world/Object3D.ts @@ -1,8 +1,8 @@ /// import Kernel = require('./Kernel'); -import Matrix = require('./Matrix'); -import Vertice = require('./Vertice'); -import Vector = require('./Vector'); +import Matrix = require('./math/Matrix'); +import Vertice = require('./math/Vertice'); +import Vector = require('./math/Vector'); import TextureMaterial = require('./TextureMaterial'); class Object3D { diff --git a/src/world/Object3DComponents.ts b/src/world/Object3DComponents.ts index 39424b1..b52fe4a 100644 --- a/src/world/Object3DComponents.ts +++ b/src/world/Object3DComponents.ts @@ -1,7 +1,7 @@ /// import Kernel = require('./Kernel'); -import Vector = require('./Vector'); -import Matrix = require('./Matrix'); +import Vector = require('./math/Vector'); +import Matrix = require('./math/Matrix'); import Object3D = require('./Object3D'); import PerspectiveCamera = require('./PerspectiveCamera'); diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index 2d90c33..09bfbeb 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -1,13 +1,13 @@ /// 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 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('./Matrix'); +import Matrix = require('./math/Matrix'); import Object3D = require('./Object3D'); import Globe = require('./Globe');//just used for TypeScript validate type @@ -308,7 +308,7 @@ class PerspectiveCamera extends Object3D { var a = timestap - start; if(a >= span){ this.matrix = newMat; - this.animating = false; + this.animating = false; cb(); }else{ var p = this.getPosition(); @@ -365,7 +365,7 @@ class PerspectiveCamera extends Object3D { dir.setLength(deltaLength); var pNew = Vector.verticePlusVector(pOld, dir); this.setPosition(pNew.x, pNew.y, pNew.z); - } + } } //判断世界坐标系中的点是否在Canvas中可见 diff --git a/src/world/Scene.ts b/src/world/Scene.ts index d2c1053..ad1d7d5 100644 --- a/src/world/Scene.ts +++ b/src/world/Scene.ts @@ -1,6 +1,6 @@ /// import Object3DComponents = require('./Object3DComponents'); -import TiledLayer = require("./TiledLayer"); +import TiledLayer = require("./layers/TiledLayer"); class Scene extends Object3DComponents{ tiledLayer: TiledLayer; diff --git a/src/world/Tile.ts b/src/world/Tile.ts index 0284d03..eabf42f 100644 --- a/src/world/Tile.ts +++ b/src/world/Tile.ts @@ -3,7 +3,7 @@ import Kernel = require('./Kernel'); import Object3D = require('./Object3D'); import Enum = require('./Enum'); import Elevation = require('./Elevation'); -import MathUtils = require('./Math'); +import MathUtils = require('./math/Math'); import TileMaterial = require('./TileMaterial'); class Tile extends Object3D { 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/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/ArcGISTiledLayer.ts b/src/world/layers/ArcGISTiledLayer.ts similarity index 74% rename from src/world/ArcGISTiledLayer.ts rename to src/world/layers/ArcGISTiledLayer.ts index c410716..a83ddcd 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{ diff --git a/src/world/AutonaviTiledLayer.ts b/src/world/layers/AutonaviTiledLayer.ts similarity index 78% rename from src/world/AutonaviTiledLayer.ts rename to src/world/layers/AutonaviTiledLayer.ts index dbb797f..12f2b1e 100644 --- a/src/world/AutonaviTiledLayer.ts +++ b/src/world/layers/AutonaviTiledLayer.ts @@ -1,5 +1,5 @@ -/// -import Kernel = require('./Kernel'); +/// +import Kernel = require('../Kernel'); import TiledLayer = require('./TiledLayer'); class AutonaviTiledLayer extends TiledLayer{ diff --git a/src/world/BingTiledLayer.ts b/src/world/layers/BingTiledLayer.ts similarity index 91% rename from src/world/BingTiledLayer.ts rename to src/world/layers/BingTiledLayer.ts index d024f8a..e928a5c 100644 --- a/src/world/BingTiledLayer.ts +++ b/src/world/layers/BingTiledLayer.ts @@ -1,5 +1,5 @@ -/// -import MathUtils = require('./Math'); +/// +import MathUtils = require('../math/Math'); import TiledLayer = require('./TiledLayer'); //Bing地图 diff --git a/src/world/BlendTiledLayer.ts b/src/world/layers/BlendTiledLayer.ts similarity index 87% rename from src/world/BlendTiledLayer.ts rename to src/world/layers/BlendTiledLayer.ts index fab27d8..8121243 100644 --- a/src/world/BlendTiledLayer.ts +++ b/src/world/layers/BlendTiledLayer.ts @@ -1,4 +1,4 @@ -/// +/// import TiledLayer = require("./TiledLayer"); import NokiaTiledLayer = require("./NokiaTiledLayer"); import GoogleTiledLayer = require("./GoogleTiledLayer"); diff --git a/src/world/GoogleTiledLayer.ts b/src/world/layers/GoogleTiledLayer.ts similarity index 82% rename from src/world/GoogleTiledLayer.ts rename to src/world/layers/GoogleTiledLayer.ts index 19d09d3..0bb800b 100644 --- a/src/world/GoogleTiledLayer.ts +++ b/src/world/layers/GoogleTiledLayer.ts @@ -1,4 +1,4 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class GoogleTiledLayer extends TiledLayer{ diff --git a/src/world/NokiaTiledLayer.ts b/src/world/layers/NokiaTiledLayer.ts similarity index 89% rename from src/world/NokiaTiledLayer.ts rename to src/world/layers/NokiaTiledLayer.ts index b5bd3cd..a594c55 100644 --- a/src/world/NokiaTiledLayer.ts +++ b/src/world/layers/NokiaTiledLayer.ts @@ -1,4 +1,4 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class NokiaTiledLayer extends TiledLayer{ diff --git a/src/world/OsmTiledLayer.ts b/src/world/layers/OsmTiledLayer.ts similarity index 83% rename from src/world/OsmTiledLayer.ts rename to src/world/layers/OsmTiledLayer.ts index 44d9b85..474dcce 100644 --- a/src/world/OsmTiledLayer.ts +++ b/src/world/layers/OsmTiledLayer.ts @@ -1,4 +1,4 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class OsmTiledLayer extends TiledLayer { diff --git a/src/world/SosoTiledLayer.ts b/src/world/layers/SosoTiledLayer.ts similarity index 90% rename from src/world/SosoTiledLayer.ts rename to src/world/layers/SosoTiledLayer.ts index 952fbf1..a27eee9 100644 --- a/src/world/SosoTiledLayer.ts +++ b/src/world/layers/SosoTiledLayer.ts @@ -1,4 +1,4 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class SosoTiledLayer extends TiledLayer { diff --git a/src/world/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts similarity index 93% rename from src/world/SubTiledLayer.ts rename to src/world/layers/SubTiledLayer.ts index c9ca3fa..06d424c 100644 --- a/src/world/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -1,11 +1,11 @@ -/// -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'); +/// +import Kernel = require('../Kernel'); +import Utils = require('../Utils'); +import MathUtils = require('../math/Math'); +import TileGrid = require('../TileGrid'); +import Object3DComponents = require('../Object3DComponents'); +import Tile = require('../Tile'); +import Elevation = require('../Elevation'); class SubTiledLayer extends Object3DComponents { level: number = -1; diff --git a/src/world/TiandituTiledLayer.ts b/src/world/layers/TiandituTiledLayer.ts similarity index 81% rename from src/world/TiandituTiledLayer.ts rename to src/world/layers/TiandituTiledLayer.ts index 65f6f94..113e9b1 100644 --- a/src/world/TiandituTiledLayer.ts +++ b/src/world/layers/TiandituTiledLayer.ts @@ -1,4 +1,4 @@ -/// +/// import TiledLayer = require('./TiledLayer'); class TiandituTiledLayer extends TiledLayer { diff --git a/src/world/TiledLayer.ts b/src/world/layers/TiledLayer.ts similarity index 89% rename from src/world/TiledLayer.ts rename to src/world/layers/TiledLayer.ts index 598f676..cc09fc6 100644 --- a/src/world/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -1,6 +1,6 @@ -/// -import Kernel = require('./Kernel'); -import Object3DComponents = require('./Object3DComponents'); +/// +import Kernel = require('../Kernel'); +import Object3DComponents = require('../Object3DComponents'); import SubTiledLayer = require('./SubTiledLayer'); abstract class TiledLayer extends Object3DComponents { 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 99% rename from src/world/Math.ts rename to src/world/math/Math.ts index f15c81f..734d7ee 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'); diff --git a/src/world/Matrix.ts b/src/world/math/Matrix.ts similarity index 99% rename from src/world/Matrix.ts rename to src/world/math/Matrix.ts index 6acce9f..7b87858 100644 --- a/src/world/Matrix.ts +++ b/src/world/math/Matrix.ts @@ -1,4 +1,4 @@ -/// +/// import Vertice = require('./Vertice'); import Vector = require('./Vector'); 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 98% rename from src/world/Vector.ts rename to src/world/math/Vector.ts index 6c366a6..fb2687d 100644 --- a/src/world/Vector.ts +++ b/src/world/math/Vector.ts @@ -1,4 +1,4 @@ -/// +/// import Vertice = require('./Vertice'); class Vector{ 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 From 8737f1ae838adb55cad1f62d80cb53065b5726d6 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 21 Oct 2016 13:43:33 +0800 Subject: [PATCH 005/109] add new ts files --- src/world/VertexBufferObject.ts | 40 ++++++++ src/world/geometries/Box.ts | 146 ++++++++++++++++++++++++++++ src/world/geometries/Geometry.ts | 161 +++++++++++++++++++++++++++++++ src/world/geometries/Triangle.ts | 13 +++ src/world/geometries/Vertice.ts | 18 ++++ 5 files changed, 378 insertions(+) create mode 100644 src/world/VertexBufferObject.ts create mode 100644 src/world/geometries/Box.ts create mode 100644 src/world/geometries/Geometry.ts create mode 100644 src/world/geometries/Triangle.ts create mode 100644 src/world/geometries/Vertice.ts diff --git a/src/world/VertexBufferObject.ts b/src/world/VertexBufferObject.ts new file mode 100644 index 0000000..49e9155 --- /dev/null +++ b/src/world/VertexBufferObject.ts @@ -0,0 +1,40 @@ +/// +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(); + } + + if(this.target === Kernel.gl.ARRAY_BUFFER){ + Kernel.gl.bufferData(Kernel.gl.ARRAY_BUFFER, new Float32Array(data), usage); + }else if(this.target === Kernel.gl.ELEMENT_ARRAY_BUFFER){ + Kernel.gl.bufferData(Kernel.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), usage); + } + } + + destroy(){ + if(Kernel.gl.isBuffer(this.buffer)){ + Kernel.gl.deleteBuffer(this.buffer); + } + this.buffer = null; + } +} + +export = VertexBufferObject; \ 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..21e8300 --- /dev/null +++ b/src/world/geometries/Box.ts @@ -0,0 +1,146 @@ +/// + +import Vertice = require("./Vertice"); +import Triangle = require("./Triangle"); +import Geometry = require("./Geometry"); + +class Box extends Geometry { + constructor(public length: number, public width: number, public height: number) { + super(); + } + + 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..22a1940 --- /dev/null +++ b/src/world/geometries/Geometry.ts @@ -0,0 +1,161 @@ +/// +import Kernel = require("../Kernel"); +import Vertice = require("./Vertice"); +import Triangle = require("./Triangle"); +import VertexBufferObject = require("../VertexBufferObject"); + +class Geometry { + vertices: Vertice[]; + triangles: Triangle[]; + vbo: any; + ibo: any; + nbo: any; + uvbo: any; + cbo: any; + + constructor() { + this.buildTriangles(); + } + + //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) { + Kernel.gl.deleteBuffer(this.vbo); + } + if (this.ibo) { + Kernel.gl.deleteBuffer(this.ibo); + } + if (this.nbo) { + Kernel.gl.deleteBuffer(this.nbo); + } + if (this.cbo) { + Kernel.gl.deleteBuffer(this.cbo); + } + if (this.uvbo) { + Kernel.gl.deleteBuffer(this.uvbo); + } + + this.vbo = null; + this.ibo = null; + this.nbo = null; + this.cbo = null; + this.uvbo = null; + this.vertices = []; + this.triangles = []; + } +} + +//world.geometries._Geometry.prototype = new world._Object3D(); + +//world.geometries._Geometry.prototype.constructor = world.geometries._Geometry; + +export = Geometry; \ 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..dd0ef14 --- /dev/null +++ b/src/world/geometries/Triangle.ts @@ -0,0 +1,13 @@ +/// +import Vertice = require("./Vertice"); + +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; + } +} + +export = Triangle; \ No newline at end of file diff --git a/src/world/geometries/Vertice.ts b/src/world/geometries/Vertice.ts new file mode 100644 index 0000000..430f13a --- /dev/null +++ b/src/world/geometries/Vertice.ts @@ -0,0 +1,18 @@ +/// +class Vertice{ + p:number[]; + n:number[]; + uv:number[]; + c:number[]; + i:number; + + constructor(args:any){ + this.p = args.p;//[x,y,z] + this.n = args.n;//[x,y,z] + this.uv = args.uv;//[s,t] + this.c = args.c;//[r,g,b] + this.i = args.i;//index + } +} + +export = Vertice; \ No newline at end of file From b982aa159469c4594a09db8d96540ec23b91bb8f Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sat, 22 Oct 2016 18:57:52 +0800 Subject: [PATCH 006/109] update new ts files --- src/world/graphics/Graphic.ts | 66 +++++++++++++ src/world/graphics/MeshGraphic.ts | 104 +++++++++++++++++++++ src/world/materials/Material.ts | 8 ++ src/world/materials/MeshColorMaterial.ts | 55 +++++++++++ src/world/materials/MeshTextureMaterial.ts | 89 ++++++++++++++++++ src/world/math/Math.ts | 4 + 6 files changed, 326 insertions(+) create mode 100644 src/world/graphics/Graphic.ts create mode 100644 src/world/graphics/MeshGraphic.ts create mode 100644 src/world/materials/Material.ts create mode 100644 src/world/materials/MeshColorMaterial.ts create mode 100644 src/world/materials/MeshTextureMaterial.ts diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts new file mode 100644 index 0000000..1622d86 --- /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"); + +interface GraphicOptions{ + geometry: Geometry; + material: Material; + parent: any; + visible?: boolean; +} + +class Graphic{ + id:number; + ready: boolean = false; + visible: boolean = true; + parent: any; + + constructor(public geometry: Geometry, public material: Material){ + this.id = ++Kernel.idCounter; + this.parent = null; + } + + setVisible(visible: boolean){ + this.visible = visible; + } + + getProgramType() { + return this.material.getType(); + } + + //need to be override + createProgram(): any{ + return null; + } + + // onBeforeDraw(){} + + // _draw(program, camera, scene){ + // if(!program || !this.visible || !this.isReady || !this.material.isReady){ + // return; + // } + + // this.onBeforeDraw(program, camera, scene); + // this.draw(program, camera, scene); + // this.onAfterDraw(program, camera, scene); + // } + + // //need to be override + // draw(){} + + // onAfterDraw(){} + + destroy(){ + this.parent = null; + //释放显卡中的资源 + this.geometry.destroy(); + this.material.destroy(); + this.geometry = null; + this.material = null; + this.ready = false; + } +} + +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..10408fa --- /dev/null +++ b/src/world/graphics/MeshGraphic.ts @@ -0,0 +1,104 @@ +/// + +import Kernel = require("../Kernel"); +import Graphic = require("./Graphic"); +import Geometry = require("../geometries/Geometry"); +import MeshTextureMaterial = require("../materials/MeshTextureMaterial"); + +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: Geometry, public material: MeshTextureMaterial){ + super(geometry, material); + this.geometry.calculateVBO(); + this.geometry.calculateIBO(); + this._init4TextureMaterial(); + this.ready = true; + } + + _init4TextureMaterial() { + this.geometry.calculateUVBO(); + } + + _drawTextureMaterial(program) { + //set aUV + var locUV = program.getAttribLocation('aUV'); + program.enableVertexAttribArray('aUV'); + this.geometry.uvbo.bind(); + Kernel.gl.vertexAttribPointer(locUV, 2, Kernel.gl.FLOAT, false, 0, 0); + + //set uSampler + var locSampler = program.getUniformLocation('uSampler'); + Kernel.gl.activeTexture(Kernel.gl.TEXTURE0); + //world.Cache.activeTexture(gl.TEXTURE0); + Kernel.gl.bindTexture(Kernel.gl.TEXTURE_2D, this.material.texture); + Kernel.gl.uniform1i(locSampler, 0); + } + + // createProgram(scene) { + // var program = null; + // var programType = this.getProgramType(scene); + // var args = { + // type: programType, + // definesObject: null, + // vsSource: vs, + // fsSource: fs + // }; + // program = new world.Program(args); + + // return program; + // } + + // draw(program, camera, scene) { + // //aPosition + // var locPosition = program.getAttribLocation('aPosition'); + // program.enableVertexAttribArray('aPosition'); + // this.geometry.vbo.bind(); + // Kernel.gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); + + // //uPMVMatrix + // var pmvMatrix = camera.projViewMatrix.multiplyMatrix(this.geometry.matrix); + // var locPMVMatrix = program.getUniformLocation('uPMVMatrix'); + // Kernel.gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); + + // this._drawTextureMaterial(program); + + // //设置索引,但不用往shader中赋值 + // this.geometry.ibo.bind(); + + // //绘图 + // var count = this.geometry.triangles.length * 3; + // Kernel.gl.drawElements(Kernel.gl.TRIANGLES, count, 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); + // } +} + +export = MeshGraphic; \ 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..2719bff --- /dev/null +++ b/src/world/materials/MeshTextureMaterial.ts @@ -0,0 +1,89 @@ +/// +import Kernel = require("../Kernel"); +import MathUtils = require("../math/Math"); +import Material = require("./Material"); + +class MeshTextureMaterial extends Material { + texture: WebGLTexture; + image: HTMLImageElement; + url: string; + ready: boolean = false; + isDelete: boolean = false; + + constructor(args: any) { + super(); + 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); + } else { + throw 'Invalid parameter'; + } + } + + getType(){ + return "MeshTextureMaterial"; + } + + isReady(){ + return this.ready; + } + + setImage(image: HTMLImageElement) { + if (image.width > 0 && image.height > 0) { + this.image = image; + this._onLoad(); + } + } + + setImageUrl(url: string) { + this.image = new Image(); + this.image.crossOrigin = 'anonymous';//很重要,因为图片是跨域获得的,所以一定要加上此句代码 + this.image.onload = this._onLoad.bind(this); + this.image.src = url; + } + + //图片加载完成时触发 + _onLoad() { + //要考虑纹理已经被移除掉了图片才进入onLoad这种情况 + if (this.isDelete) { + return; + } + + Kernel.gl.bindTexture(Kernel.gl.TEXTURE_2D, this.texture); + //gl.pixelStorei(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); + + var isMipMap = this.image.width === this.image.height && MathUtils.isPowerOfTwo(this.image.width); + + if (isMipMap) { + //使用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_MIPMAP_NEAREST);//LINEAR_MIPMAP_LINEAR + Kernel.gl.generateMipmap(Kernel.gl.TEXTURE_2D); + } else { + Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MIN_FILTER, Kernel.gl.LINEAR);//gl.NEAREST + Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MAG_FILTER, Kernel.gl.LINEAR);//gl.NEAREST + } + + 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.bindTexture(Kernel.gl.TEXTURE_2D, null); + + this.ready = true; + } + + //释放显卡中的texture资源 + destroy() { + if (Kernel.gl.isTexture(this.texture)) { + Kernel.gl.deleteTexture(this.texture); + } + this.texture = null; + this.isDelete = true; + this.ready = false; + } +} + +export = MeshTextureMaterial; \ No newline at end of file diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index 734d7ee..b9603e7 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -25,6 +25,10 @@ const MathUtils = { return Math.abs(value) < 0.000001; }, + isPowerOfTwo(value: number) { + return ( value & ( value - 1 ) ) === 0 && value !== 0; + }, + /** * 将其他进制的数字转换为10进制 * @param numSys 要准换的进制 From f01348ab887b1edbdf432622ac6033c1fcee05e2 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Tue, 25 Oct 2016 13:40:56 +0800 Subject: [PATCH 007/109] update ts files --- src/world/Object3D.ts | 147 +-------------------------- src/world/Object3DComponents.ts | 9 +- src/world/PerspectiveCamera.ts | 2 +- src/world/Program.ts | 162 ++++++++++++++++++++++++++++++ src/world/ProgramUtils.ts | 31 ++++++ src/world/geometries/Geometry.ts | 28 +++--- src/world/graphics/Graphic.ts | 29 ++---- src/world/graphics/MeshGraphic.ts | 74 ++++++-------- 8 files changed, 258 insertions(+), 224 deletions(-) create mode 100644 src/world/Program.ts create mode 100644 src/world/ProgramUtils.ts diff --git a/src/world/Object3D.ts b/src/world/Object3D.ts index b7ff2c1..2b818e2 100644 --- a/src/world/Object3D.ts +++ b/src/world/Object3D.ts @@ -6,153 +6,10 @@ import Vector = require('./math/Vector'); import TextureMaterial = require('./TextureMaterial'); 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; + + constructor() { 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; - } } //需要子类重写 diff --git a/src/world/Object3DComponents.ts b/src/world/Object3DComponents.ts index b52fe4a..7470212 100644 --- a/src/world/Object3DComponents.ts +++ b/src/world/Object3DComponents.ts @@ -3,9 +3,10 @@ import Kernel = require('./Kernel'); import Vector = require('./math/Vector'); import Matrix = require('./math/Matrix'); import Object3D = require('./Object3D'); +import Graphic = require('./graphics/Graphic'); import PerspectiveCamera = require('./PerspectiveCamera'); -type ChildType = Object3D | Object3DComponents; +// type ChildType = Object3D | Object3DComponents; //三维对象集合 class Object3DComponents { @@ -13,7 +14,7 @@ class Object3DComponents { matrix: Matrix; visible: boolean; parent: any; - children: ChildType[]; + children: Graphic[]; constructor() { this.id = ++Kernel.idCounter; @@ -23,7 +24,7 @@ class Object3DComponents { this.children = []; } - add(obj: ChildType) { + add(obj: Graphic) { if (this.findObjById(obj.id) !== null) { console.debug("obj已经存在于Object3DComponents中,无法将其再次加入!"); return; @@ -33,7 +34,7 @@ class Object3DComponents { } } - remove(obj: ChildType) { + remove(obj: Graphic) { if (obj) { var result = this.findObjById(obj.id); if (result === null) { diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index 09bfbeb..1e92d06 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -22,7 +22,7 @@ class PerspectiveCamera extends Object3D { private animating: boolean = false; constructor(public fov = 90, public aspect = 1, public near = 1, public far = 1) { - super(null); + super(); this.pitch = 90; this.projMatrix = new Matrix(); this.setPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); diff --git a/src/world/Program.ts b/src/world/Program.ts new file mode 100644 index 0000000..e5ef5a5 --- /dev/null +++ b/src/world/Program.ts @@ -0,0 +1,162 @@ +/// + +import Kernel = require("./Kernel"); + +class Program{ + ready: boolean = false; + activeInfosObject:any; + program: WebGLProgram; + static currentProgram: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(); + } + + 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; 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; 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){ + //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){ + var loc = -1; + var activeInfo = this.activeInfosObject[name]; + if(activeInfo && activeInfo.isAttribute){ + loc = activeInfo.loc; + } + return loc; + } + + //return WebGLUniformLocation, not a number + getUniformLocation(name){ + var loc = null; + 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){ + var result = null; + var loc = this.getUniformLocation(name); + if(loc){ + result = Kernel.gl.getUniform(this.program, loc); + } + return result; + } + + enableVertexAttribArray(name){ + 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){ + 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, shaderText){ + 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/ProgramUtils.ts b/src/world/ProgramUtils.ts new file mode 100644 index 0000000..ed95f5e --- /dev/null +++ b/src/world/ProgramUtils.ts @@ -0,0 +1,31 @@ +/// + +import Program = require("./Program"); +import Graphic = require("./graphics/Graphic"); + +const programs:Program[] = []; + +const ProgramUtils = { + + getProgram(graphic){ + var program:Program = null; + + var programType = graphic.getProgramType(); + + programs.some(function(item){ + if(item.type === graphic.type){ + program = item; + return true; + }else{ + return false; + } + }); + + if(!program){ + program = graphic.createProgram(); + this.programs.push(program); + } + + return program; + } +}; \ No newline at end of file diff --git a/src/world/geometries/Geometry.ts b/src/world/geometries/Geometry.ts index 22a1940..8a37f21 100644 --- a/src/world/geometries/Geometry.ts +++ b/src/world/geometries/Geometry.ts @@ -2,18 +2,20 @@ import Kernel = require("../Kernel"); import Vertice = require("./Vertice"); import Triangle = require("./Triangle"); +import Object3D = require("../Object3D"); import VertexBufferObject = require("../VertexBufferObject"); -class Geometry { +class Geometry extends Object3D { vertices: Vertice[]; triangles: Triangle[]; - vbo: any; - ibo: any; - nbo: any; - uvbo: any; - cbo: any; + vbo: VertexBufferObject; + ibo: VertexBufferObject; + nbo: VertexBufferObject; + uvbo: VertexBufferObject; + cbo: VertexBufferObject; constructor() { + super(); this.buildTriangles(); } @@ -129,19 +131,19 @@ class Geometry { destroy() { if (this.vbo) { - Kernel.gl.deleteBuffer(this.vbo); + this.vbo.destroy(); } if (this.ibo) { - Kernel.gl.deleteBuffer(this.ibo); + this.ibo.destroy(); } if (this.nbo) { - Kernel.gl.deleteBuffer(this.nbo); + this.nbo.destroy(); } if (this.cbo) { - Kernel.gl.deleteBuffer(this.cbo); + this.cbo.destroy(); } if (this.uvbo) { - Kernel.gl.deleteBuffer(this.uvbo); + this.uvbo.destroy(); } this.vbo = null; @@ -154,8 +156,4 @@ class Geometry { } } -//world.geometries._Geometry.prototype = new world._Object3D(); - -//world.geometries._Geometry.prototype.constructor = world.geometries._Geometry; - export = Geometry; \ No newline at end of file diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts index 1622d86..6f1ed93 100644 --- a/src/world/graphics/Graphic.ts +++ b/src/world/graphics/Graphic.ts @@ -3,6 +3,8 @@ import Kernel = require("../Kernel"); import Geometry = require("../geometries/Geometry"); import Material = require("../materials/Material"); +import Program = require("../Program"); +import PerspectiveCamera = require("../PerspectiveCamera"); interface GraphicOptions{ geometry: Geometry; @@ -11,7 +13,7 @@ interface GraphicOptions{ visible?: boolean; } -class Graphic{ +abstract class Graphic{ id:number; ready: boolean = false; visible: boolean = true; @@ -30,27 +32,18 @@ class Graphic{ return this.material.getType(); } - //need to be override - createProgram(): any{ - return null; + isDrawable(): boolean{ + if(!this.visible || !this.material.isReady() || !this.ready){ + return false; + } + return true; } - // onBeforeDraw(){} - - // _draw(program, camera, scene){ - // if(!program || !this.visible || !this.isReady || !this.material.isReady){ - // return; - // } - - // this.onBeforeDraw(program, camera, scene); - // this.draw(program, camera, scene); - // this.onAfterDraw(program, camera, scene); - // } + //need to be override + abstract createProgram(): Program // //need to be override - // draw(){} - - // onAfterDraw(){} + abstract draw(program: Program, camera: PerspectiveCamera) destroy(){ this.parent = null; diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index 10408fa..cf2bbd3 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -1,9 +1,11 @@ /// import Kernel = require("../Kernel"); +import Program = require("../Program"); import Graphic = require("./Graphic"); import Geometry = require("../geometries/Geometry"); import MeshTextureMaterial = require("../materials/MeshTextureMaterial"); +import PerspectiveCamera = require("../PerspectiveCamera"); const vs = ` @@ -40,11 +42,15 @@ class MeshGraphic extends Graphic { this.ready = true; } + createProgram(): Program{ + return new Program(this.getProgramType(), vs, fs); + } + _init4TextureMaterial() { this.geometry.calculateUVBO(); } - _drawTextureMaterial(program) { + _drawTextureMaterial(program: any) { //set aUV var locUV = program.getAttribLocation('aUV'); program.enableVertexAttribArray('aUV'); @@ -59,46 +65,32 @@ class MeshGraphic extends Graphic { Kernel.gl.uniform1i(locSampler, 0); } - // createProgram(scene) { - // var program = null; - // var programType = this.getProgramType(scene); - // var args = { - // type: programType, - // definesObject: null, - // vsSource: vs, - // fsSource: fs - // }; - // program = new world.Program(args); - - // return program; - // } - - // draw(program, camera, scene) { - // //aPosition - // var locPosition = program.getAttribLocation('aPosition'); - // program.enableVertexAttribArray('aPosition'); - // this.geometry.vbo.bind(); - // Kernel.gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); - - // //uPMVMatrix - // var pmvMatrix = camera.projViewMatrix.multiplyMatrix(this.geometry.matrix); - // var locPMVMatrix = program.getUniformLocation('uPMVMatrix'); - // Kernel.gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); - - // this._drawTextureMaterial(program); - - // //设置索引,但不用往shader中赋值 - // this.geometry.ibo.bind(); - - // //绘图 - // var count = this.geometry.triangles.length * 3; - // Kernel.gl.drawElements(Kernel.gl.TRIANGLES, count, 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); - // } + draw(program: Program, camera: PerspectiveCamera) { + //aPosition + var locPosition = program.getAttribLocation('aPosition'); + program.enableVertexAttribArray('aPosition'); + this.geometry.vbo.bind(); + Kernel.gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); + + //uPMVMatrix + var pmvMatrix = camera.projViewMatrix.multiplyMatrix(this.geometry.matrix); + var locPMVMatrix = program.getUniformLocation('uPMVMatrix'); + Kernel.gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); + + this._drawTextureMaterial(program); + + //设置索引,但不用往shader中赋值 + this.geometry.ibo.bind(); + + //绘图 + var count = this.geometry.triangles.length * 3; + Kernel.gl.drawElements(Kernel.gl.TRIANGLES, count, 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); + } } export = MeshGraphic; \ No newline at end of file From 4864f82b48b4a8dac83197e35f8b680e2a71053b Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 26 Oct 2016 12:41:38 +0800 Subject: [PATCH 008/109] update refactor --- src/world/GraphicGroup.ts | 76 +++++++++++++++++++++++++++++++ src/world/ProgramUtils.ts | 10 ++-- src/world/graphics/Graphic.ts | 16 +++++-- src/world/graphics/MeshGraphic.ts | 10 ++-- 4 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 src/world/GraphicGroup.ts diff --git a/src/world/GraphicGroup.ts b/src/world/GraphicGroup.ts new file mode 100644 index 0000000..0417d68 --- /dev/null +++ b/src/world/GraphicGroup.ts @@ -0,0 +1,76 @@ +/// +import Kernel = require("./Kernel"); +import Graphic = require("./graphics/Graphic"); +import PerspectiveCamera = require("./PerspectiveCamera"); + +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){ + this.children.push(g); + g.parent = this; + } + + remove(g: Drawable){ + var findResult = this.findGraphicById(g.id); + if(findResult){ + g.destroy(); + this.children.splice(findResult.index, 1); + g = null; + } + } + + 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: PerspectiveCamera){ + 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/ProgramUtils.ts b/src/world/ProgramUtils.ts index ed95f5e..a93f62b 100644 --- a/src/world/ProgramUtils.ts +++ b/src/world/ProgramUtils.ts @@ -1,4 +1,4 @@ -/// +/// import Program = require("./Program"); import Graphic = require("./graphics/Graphic"); @@ -7,13 +7,13 @@ const programs:Program[] = []; const ProgramUtils = { - getProgram(graphic){ + getProgram(graphic: Graphic){ var program:Program = null; var programType = graphic.getProgramType(); programs.some(function(item){ - if(item.type === graphic.type){ + if(item.type === graphic.getProgramType()){ program = item; return true; }else{ @@ -28,4 +28,6 @@ const ProgramUtils = { return program; } -}; \ No newline at end of file +}; + +export = ProgramUtils; \ No newline at end of file diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts index 6f1ed93..657bb4d 100644 --- a/src/world/graphics/Graphic.ts +++ b/src/world/graphics/Graphic.ts @@ -4,6 +4,7 @@ import Kernel = require("../Kernel"); import Geometry = require("../geometries/Geometry"); import Material = require("../materials/Material"); import Program = require("../Program"); +import ProgramUtils = require("../ProgramUtils"); import PerspectiveCamera = require("../PerspectiveCamera"); interface GraphicOptions{ @@ -18,16 +19,20 @@ abstract class Graphic{ ready: boolean = false; visible: boolean = true; parent: any; + program: Program; constructor(public geometry: Geometry, public material: Material){ this.id = ++Kernel.idCounter; this.parent = null; + this.program = ProgramUtils.getProgram(this); } setVisible(visible: boolean){ this.visible = visible; } + abstract createProgram(): Program + getProgramType() { return this.material.getType(); } @@ -39,11 +44,14 @@ abstract class Graphic{ return true; } - //need to be override - abstract createProgram(): Program + draw(camera: PerspectiveCamera){ + if(this.isDrawable()){ + this.program.use(); + this.onDraw(camera); + } + } - // //need to be override - abstract draw(program: Program, camera: PerspectiveCamera) + abstract onDraw(camera: PerspectiveCamera) destroy(){ this.parent = null; diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index cf2bbd3..b917025 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -65,19 +65,19 @@ class MeshGraphic extends Graphic { Kernel.gl.uniform1i(locSampler, 0); } - draw(program: Program, camera: PerspectiveCamera) { + onDraw(camera: PerspectiveCamera) { //aPosition - var locPosition = program.getAttribLocation('aPosition'); - program.enableVertexAttribArray('aPosition'); + var locPosition = this.program.getAttribLocation('aPosition'); + this.program.enableVertexAttribArray('aPosition'); this.geometry.vbo.bind(); Kernel.gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); //uPMVMatrix var pmvMatrix = camera.projViewMatrix.multiplyMatrix(this.geometry.matrix); - var locPMVMatrix = program.getUniformLocation('uPMVMatrix'); + var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); Kernel.gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); - this._drawTextureMaterial(program); + this._drawTextureMaterial(this.program); //设置索引,但不用往shader中赋值 this.geometry.ibo.bind(); From 1de06440134ab8ccadbdd29300a50da053b18339 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 26 Oct 2016 12:49:15 +0800 Subject: [PATCH 009/109] update ts files --- src/world/PerspectiveCamera.ts | 1 + src/world/Program.ts | 22 +++++++++++----------- src/world/graphics/Graphic.ts | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index 1e92d06..858e646 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -15,6 +15,7 @@ class PerspectiveCamera extends Object3D { pitch: number; viewMatrix: Matrix; projMatrix: Matrix; + projViewMatrix: Matrix; Enum: any = { EARTH_FULL_OVERSPREAD_SCREEN: "EARTH_FULL_OVERSPREAD_SCREEN", //Canvas内全部被地球充满 EARTH_NOT_FULL_OVERSPREAD_SCREEN: "EARTH_NOT_FULL_OVERSPREAD_SCREEN" //Canvas没有全部被地球充满 diff --git a/src/world/Program.ts b/src/world/Program.ts index e5ef5a5..831b59e 100644 --- a/src/world/Program.ts +++ b/src/world/Program.ts @@ -25,7 +25,7 @@ class Program{ updateActiveAttribInfos(){ var count = Kernel.gl.getProgramParameter(this.program, Kernel.gl.ACTIVE_ATTRIBUTES); - for(var i = 0, activeInfo; i < count; i++){ + 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; @@ -36,7 +36,7 @@ class Program{ updateActiveUniformInfos(){ var count = Kernel.gl.getProgramParameter(this.program, Kernel.gl.ACTIVE_UNIFORMS); - for(var i = 0, activeInfo; i < count; i++){ + 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; @@ -44,7 +44,7 @@ class Program{ } } - getLocation(name){ + getLocation(name: string){ //loc = gl.getAttribLocation(this.program, name); //loc = gl.getUniformLocation(this.program, name); var loc = -1; @@ -55,7 +55,7 @@ class Program{ return loc; } - getAttribLocation(name){ + getAttribLocation(name: string){ var loc = -1; var activeInfo = this.activeInfosObject[name]; if(activeInfo && activeInfo.isAttribute){ @@ -65,8 +65,8 @@ class Program{ } //return WebGLUniformLocation, not a number - getUniformLocation(name){ - var loc = null; + getUniformLocation(name: string){ + var loc: WebGLUniformLocation; var activeInfo = this.activeInfosObject[name]; if(activeInfo && !activeInfo.isAttribute){ loc = activeInfo.loc; @@ -87,8 +87,8 @@ class Program{ //Return the uniform value at the passed location in the passed program. //The type returned is dependent on the uniform type. - getUniform(name){ - var result = null; + getUniform(name:string){ + var result: any; var loc = this.getUniformLocation(name); if(loc){ result = Kernel.gl.getUniform(this.program, loc); @@ -96,7 +96,7 @@ class Program{ return result; } - enableVertexAttribArray(name){ + enableVertexAttribArray(name:string){ var activeInfo = this.activeInfosObject[name]; if(activeInfo && activeInfo.isAttribute && activeInfo.isEnabled !== true){ var loc = activeInfo.loc; @@ -105,7 +105,7 @@ class Program{ } } - disableVertexAttribArray(name){ + disableVertexAttribArray(name:string){ var activeInfo = this.activeInfosObject[name]; if(activeInfo && activeInfo.isAttribute && activeInfo.isEnabled !== false){ var loc = activeInfo.loc; @@ -144,7 +144,7 @@ class Program{ this.ready = true; } - _getShader(shaderType, shaderText){ + _getShader(shaderType:number, shaderText:string){ var shader = Kernel.gl.createShader(shaderType); Kernel.gl.shaderSource(shader, shaderText); Kernel.gl.compileShader(shader); diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts index 657bb4d..eaf1e35 100644 --- a/src/world/graphics/Graphic.ts +++ b/src/world/graphics/Graphic.ts @@ -51,7 +51,7 @@ abstract class Graphic{ } } - abstract onDraw(camera: PerspectiveCamera) + abstract onDraw(camera: PerspectiveCamera):void destroy(){ this.parent = null; From 0fa4e4911f4776db0cc171b97f2d8bee9789c50c Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 26 Oct 2016 13:27:34 +0800 Subject: [PATCH 010/109] update refactor --- src/world/Tile.ts | 55 ++++++++++++++-------- src/world/graphics/MeshGraphic.ts | 6 +-- src/world/materials/MeshTextureMaterial.ts | 3 +- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/world/Tile.ts b/src/world/Tile.ts index eabf42f..80036c9 100644 --- a/src/world/Tile.ts +++ b/src/world/Tile.ts @@ -1,17 +1,19 @@ /// import Kernel = require('./Kernel'); -import Object3D = require('./Object3D'); import Enum = require('./Enum'); import Elevation = require('./Elevation'); import MathUtils = require('./math/Math'); +import MeshGraphic = require('./graphics/MeshGraphic'); import TileMaterial = require('./TileMaterial'); +import Geometry = require("./geometries/Geometry"); -class Tile extends Object3D { - level: number = 0; - row: number = 0; - column: number = 0; - url: string; - subTiledLayer: any; +class TileGeometry extends Geometry{ + buildTriangles(){ + + } +} + +class TileInfo{ //type如果是GLOBE_TILE,表示其buffer已经设置为一般形式 //type如果是TERRAIN_TILE,表示其buffer已经设置为高程形式 //type如果是UNKNOWN,表示buffer没设置 @@ -28,19 +30,7 @@ class Tile extends Object3D { 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(); - } + constructor(public level: number, public row: number, public column: number, public url: string){} // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 setTileInfo(args: any) { @@ -68,6 +58,31 @@ class Tile extends Object3D { }; this.material = new TileMaterial(matArgs); } +} + +class Tile extends MeshGraphic { + subTiledLayer: any; + + + //args中包含level、row、column、url即可 + constructor(public geometry: Geometry, public material: MeshTextureMaterial) { + super(geometry, material); + // this.createVerticeData(args); + } + + static getTile(){ + + } + + createVerticeData(args: any) { + if (!args) { + return; + } + this.setTileInfo(args); + this.checkTerrain(); + } + + /** * 判断是否满足现实Terrain的条件,若满足则转换为三维地形 diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index b917025..f9efd0c 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -38,7 +38,7 @@ class MeshGraphic extends Graphic { super(geometry, material); this.geometry.calculateVBO(); this.geometry.calculateIBO(); - this._init4TextureMaterial(); + this.geometry.calculateUVBO(); this.ready = true; } @@ -46,10 +46,6 @@ class MeshGraphic extends Graphic { return new Program(this.getProgramType(), vs, fs); } - _init4TextureMaterial() { - this.geometry.calculateUVBO(); - } - _drawTextureMaterial(program: any) { //set aUV var locUV = program.getAttribLocation('aUV'); diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts index 2719bff..456e325 100644 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/world/materials/MeshTextureMaterial.ts @@ -16,8 +16,6 @@ class MeshTextureMaterial extends Material { this.setImage(args.image); } else if (typeof args.url === "string") { this.setImageUrl(args.url); - } else { - throw 'Invalid parameter'; } } @@ -39,6 +37,7 @@ class MeshTextureMaterial extends Material { setImageUrl(url: string) { this.image = new Image(); this.image.crossOrigin = 'anonymous';//很重要,因为图片是跨域获得的,所以一定要加上此句代码 + this.ready = false; this.image.onload = this._onLoad.bind(this); this.image.src = url; } From 0be901a5e799f0d2ae484d3fc016f468d14b4ac7 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 26 Oct 2016 18:05:29 +0800 Subject: [PATCH 011/109] update refactor --- src/world/TextureMaterial.ts | 6 +- src/world/Tile.ts | 245 ------------------------------- src/world/geometries/Box.ts | 7 +- src/world/geometries/Geometry.ts | 5 - src/world/graphics/Tile.ts | 226 ++++++++++++++++++++++++++++ 5 files changed, 233 insertions(+), 256 deletions(-) delete mode 100644 src/world/Tile.ts create mode 100644 src/world/graphics/Tile.ts diff --git a/src/world/TextureMaterial.ts b/src/world/TextureMaterial.ts index c571314..308108e 100644 --- a/src/world/TextureMaterial.ts +++ b/src/world/TextureMaterial.ts @@ -24,14 +24,14 @@ class TextureMaterial { 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 { diff --git a/src/world/Tile.ts b/src/world/Tile.ts deleted file mode 100644 index 80036c9..0000000 --- a/src/world/Tile.ts +++ /dev/null @@ -1,245 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Enum = require('./Enum'); -import Elevation = require('./Elevation'); -import MathUtils = require('./math/Math'); -import MeshGraphic = require('./graphics/MeshGraphic'); -import TileMaterial = require('./TileMaterial'); -import Geometry = require("./geometries/Geometry"); - -class TileGeometry extends Geometry{ - buildTriangles(){ - - } -} - -class TileInfo{ - //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; - - constructor(public level: number, public row: number, public column: number, public url: string){} - - // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 - 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); - } -} - -class Tile extends MeshGraphic { - subTiledLayer: any; - - - //args中包含level、row、column、url即可 - constructor(public geometry: Geometry, public material: MeshTextureMaterial) { - super(geometry, material); - // this.createVerticeData(args); - } - - static getTile(){ - - } - - createVerticeData(args: any) { - if (!args) { - return; - } - this.setTileInfo(args); - this.checkTerrain(); - } - - - - /** - * 判断是否满足现实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/geometries/Box.ts b/src/world/geometries/Box.ts index 21e8300..184b50e 100644 --- a/src/world/geometries/Box.ts +++ b/src/world/geometries/Box.ts @@ -7,6 +7,7 @@ import Geometry = require("./Geometry"); class Box extends Geometry { constructor(public length: number, public width: number, public height: number) { super(); + this.buildTriangles(); } buildTriangles() { @@ -22,9 +23,9 @@ class Box extends Geometry { / | / | F1----F3 | | B2- |--B4 - |/ | / - F2----F4 - */ + |/ | / + F2----F4 + */ //前面四个顶点 var vF1 = [-halfLength, halfHeight, halfWidth]; //前面左上角点 F1,索引0 diff --git a/src/world/geometries/Geometry.ts b/src/world/geometries/Geometry.ts index 8a37f21..efdf4f5 100644 --- a/src/world/geometries/Geometry.ts +++ b/src/world/geometries/Geometry.ts @@ -14,11 +14,6 @@ class Geometry extends Object3D { uvbo: VertexBufferObject; cbo: VertexBufferObject; - constructor() { - super(); - this.buildTriangles(); - } - //set vertices and triangles buildTriangles(){ this.vertices = []; diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts new file mode 100644 index 0000000..5def207 --- /dev/null +++ b/src/world/graphics/Tile.ts @@ -0,0 +1,226 @@ +/// +import Kernel = require('../Kernel'); +import Enum = require('../Enum'); +import Elevation = require('../Elevation'); +import MathUtils = require('../math/Math'); +import MeshGraphic = require('../graphics/MeshGraphic'); +import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); +import Geometry = require("../geometries/Geometry"); +import Vertice = require("../geometries/Vertice"); +import Triangle = require("../geometries/Triangle"); + +class TileGeometry extends Geometry{ + constructor(public tileInfo: TileInfo){ + super(); + } + + buildTriangles(){ + + } +} + +class TileInfo{ + //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; + geometry: TileGeometry; + material: MeshTextureMaterial; + + constructor(public level: number, public row: number, public column: number, public url: string){ + this._setTileInfo(); + this._createGeometry(); + this._createMaterial(); + } + + // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 + _setTileInfo() { + 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]; + } + + _createGeometry(){ + this.geometry = null; + } + + _createMaterial(){ + var matArgs = { + level: this.level, + url: this.url + }; + this.material = new MeshTextureMaterial(matArgs); + } +} + +class Tile extends MeshGraphic { + subTiledLayer: any; + + private constructor(public geometry: TileGeometry, public material: MeshTextureMaterial, public tileInfo: TileInfo) { + super(geometry, material); + } + + static getTile(level: number, row: number, column: number, url: string){ + var tileInfo = new TileInfo(level, row, column, url); + return new Tile(tileInfo.geometry, tileInfo.material, tileInfo); + } + + // createVerticeData(args: any) { + // if (!args) { + // return; + // } + // this.setTileInfo(args); + // this.checkTerrain(); + // } + + /** + * 判断是否满足现实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.tileInfo.level >= Kernel.TERRAIN_LEVEL; + // var shouldShowTerrain = this.tileInfo.type != Enum.TERRAIN_TILE && a && globe && globe.camera && globe.camera.pitch != 90; + // if (shouldShowTerrain) { + // //应该以TerrainTile显示 + // if (!this.tileInfo.elevationInfo) { + // this.tileInfo.elevationInfo = Elevation.getExactElevation(this.tileInfo.level, this.tileInfo.row, this.tileInfo.column); + // } + // var canShowTerrain = this.tileInfo.elevationInfo ? true : false; + // if (canShowTerrain) { + // //能够显示为TerrainTile + // this.handleTerrainTile(); + // } else { + // //不能够显示为TerrainTile + // this.visible = false; + // //this.handleGlobeTile(); + // } + // } else { + // if (this.tileInfo.type == Enum.UNKNOWN) { + // //初始type为UNKNOWN,还未初始化buffer,应该显示为GlobeTile + // this.handleGlobeTile(); + // } + // } + } + + //处理球面的切片 + handleGlobeTile() { + this.tileInfo.type = Enum.GLOBE_TILE; + if (this.tileInfo.level < Kernel.BASE_LEVEL) { + var changeLevel = Kernel.BASE_LEVEL - this.tileInfo.level; + this.tileInfo.segment = Math.pow(2, changeLevel); + } else { + this.tileInfo.segment = 1; + } + this.handleTile(); + } + + //处理地形的切片 + handleTerrainTile() { + this.tileInfo.type = Enum.TERRAIN_TILE; + this.tileInfo.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.tileInfo.maxX - this.tileInfo.minX) / this.tileInfo.segment; + var deltaY = (this.tileInfo.maxY - this.tileInfo.minY) / this.tileInfo.segment; + var deltaTextureCoord = 1.0 / this.tileInfo.segment; + var changeElevation = this.tileInfo.type == Enum.TERRAIN_TILE && this.tileInfo.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.tileInfo.segment; i++) { + mercatorXs.push(this.tileInfo.minX + i * deltaX); + mercatorYs.push(this.tileInfo.maxY - i * deltaY); + var b = i * deltaTextureCoord; + textureSs.push(b); + textureTs.push(1 - b); + } + //从左上到右下遍历填充vertices和textureCoords:从最上面一行开始自左向右遍历一行,然后再以相同的方式遍历下面一行 + for (i = 0; i <= this.tileInfo.segment; i++) { + for (j = 0; j <= this.tileInfo.segment; j++) { + var merX = mercatorXs[j]; + var merY = mercatorYs[i]; + var ele = changeElevation ? this.tileInfo.elevationInfo.elevations[(this.tileInfo.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.tileInfo.segment; i++) { + for (j = 0; j < this.tileInfo.segment; j++) { + var idx0 = (this.tileInfo.segment + 1) * i + j; + var idx1 = (this.tileInfo.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 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 From fd2ddd91d1832c50584cf8183896654fcdfe256e Mon Sep 17 00:00:00 2001 From: iSpring Date: Wed, 26 Oct 2016 21:37:15 +0800 Subject: [PATCH 012/109] update refactor --- src/world/graphics/Graphic.ts | 11 +- src/world/graphics/Tile.ts | 214 +++++++++++++++++++--------------- 2 files changed, 122 insertions(+), 103 deletions(-) diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts index eaf1e35..392349f 100644 --- a/src/world/graphics/Graphic.ts +++ b/src/world/graphics/Graphic.ts @@ -16,7 +16,6 @@ interface GraphicOptions{ abstract class Graphic{ id:number; - ready: boolean = false; visible: boolean = true; parent: any; program: Program; @@ -37,11 +36,12 @@ abstract class Graphic{ return this.material.getType(); } + isReady(): boolean{ + return this.geometry && this.material && this.material.isReady(); + } + isDrawable(): boolean{ - if(!this.visible || !this.material.isReady() || !this.ready){ - return false; - } - return true; + return this.visible && this.isReady(); } draw(camera: PerspectiveCamera){ @@ -60,7 +60,6 @@ abstract class Graphic{ this.material.destroy(); this.geometry = null; this.material = null; - this.ready = false; } } diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index 5def207..328e547 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -9,17 +9,17 @@ import Geometry = require("../geometries/Geometry"); import Vertice = require("../geometries/Vertice"); import Triangle = require("../geometries/Triangle"); -class TileGeometry extends Geometry{ - constructor(public tileInfo: TileInfo){ +class TileGeometry extends Geometry { + constructor(public vertices: Vertice[], public triangles: Triangle[]) { super(); } - buildTriangles(){ + // buildTriangles() { - } + // } } -class TileInfo{ +class TileInfo { //type如果是GLOBE_TILE,表示其buffer已经设置为一般形式 //type如果是TERRAIN_TILE,表示其buffer已经设置为高程形式 //type如果是UNKNOWN,表示buffer没设置 @@ -37,9 +37,11 @@ class TileInfo{ elevationInfo: any = null; geometry: TileGeometry; material: MeshTextureMaterial; + visible: boolean; - constructor(public level: number, public row: number, public column: number, public url: string){ + constructor(public level: number, public row: number, public column: number, public url: string) { this._setTileInfo(); + this._checkTerrain(); this._createGeometry(); this._createMaterial(); } @@ -47,6 +49,7 @@ class TileInfo{ // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 _setTileInfo() { this.elevationLevel = Elevation.getAncestorElevationLevel(this.level); + //经纬度范围 var Egeo = MathUtils.getTileGeographicEnvelopByGrid(this.level, this.row, this.column); this.minLon = Egeo.minLon; @@ -55,6 +58,7 @@ class TileInfo{ 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]; @@ -62,38 +66,17 @@ class TileInfo{ this.maxY = maxCoord[1]; } - _createGeometry(){ + _createGeometry() { this.geometry = null; } - _createMaterial(){ + _createMaterial() { var matArgs = { level: this.level, url: this.url }; this.material = new MeshTextureMaterial(matArgs); } -} - -class Tile extends MeshGraphic { - subTiledLayer: any; - - private constructor(public geometry: TileGeometry, public material: MeshTextureMaterial, public tileInfo: TileInfo) { - super(geometry, material); - } - - static getTile(level: number, row: number, column: number, url: string){ - var tileInfo = new TileInfo(level, row, column, url); - return new Tile(tileInfo.geometry, tileInfo.material, tileInfo); - } - - // createVerticeData(args: any) { - // if (!args) { - // return; - // } - // this.setTileInfo(args); - // this.checkTerrain(); - // } /** * 判断是否满足现实Terrain的条件,若满足则转换为三维地形 @@ -104,91 +87,103 @@ class Tile extends MeshGraphic { * 4.当前切片的高程数据存在 * 5.如果bForce为true,则表示强制显示为三维,不考虑level */ - checkTerrain(bForce: boolean = false) { - // var globe = Kernel.globe; - // var a = bForce === true ? true : this.tileInfo.level >= Kernel.TERRAIN_LEVEL; - // var shouldShowTerrain = this.tileInfo.type != Enum.TERRAIN_TILE && a && globe && globe.camera && globe.camera.pitch != 90; - // if (shouldShowTerrain) { - // //应该以TerrainTile显示 - // if (!this.tileInfo.elevationInfo) { - // this.tileInfo.elevationInfo = Elevation.getExactElevation(this.tileInfo.level, this.tileInfo.row, this.tileInfo.column); - // } - // var canShowTerrain = this.tileInfo.elevationInfo ? true : false; - // if (canShowTerrain) { - // //能够显示为TerrainTile - // this.handleTerrainTile(); - // } else { - // //不能够显示为TerrainTile - // this.visible = false; - // //this.handleGlobeTile(); - // } - // } else { - // if (this.tileInfo.type == Enum.UNKNOWN) { - // //初始type为UNKNOWN,还未初始化buffer,应该显示为GlobeTile - // this.handleGlobeTile(); - // } - // } + _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); + } + 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.tileInfo.type = Enum.GLOBE_TILE; - if (this.tileInfo.level < Kernel.BASE_LEVEL) { - var changeLevel = Kernel.BASE_LEVEL - this.tileInfo.level; - this.tileInfo.segment = Math.pow(2, changeLevel); + _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.tileInfo.segment = 1; + this.segment = 1; } - this.handleTile(); + this._handleTile(); } //处理地形的切片 - handleTerrainTile() { - this.tileInfo.type = Enum.TERRAIN_TILE; - this.tileInfo.segment = 10; - this.handleTile(); - }; + _handleTerrainTile() { + this.type = Enum.TERRAIN_TILE; + this.segment = 10; + this._handleTile(); + } //如果是GlobeTile,那么elevations为null //如果是TerrainTile,那么elevations是一个一维数组,大小是(segment+1)*(segment+1) - handleTile() { + _handleTile() { this.visible = true; - var vertices:number[] = []; - var indices:number[] = []; - var textureCoords:number[] = []; - - var deltaX = (this.tileInfo.maxX - this.tileInfo.minX) / this.tileInfo.segment; - var deltaY = (this.tileInfo.maxY - this.tileInfo.minY) / this.tileInfo.segment; - var deltaTextureCoord = 1.0 / this.tileInfo.segment; - var changeElevation = this.tileInfo.type == Enum.TERRAIN_TILE && this.tileInfo.elevationInfo; + 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 * 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.tileInfo.segment; i++) { - mercatorXs.push(this.tileInfo.minX + i * deltaX); - mercatorYs.push(this.tileInfo.maxY - i * deltaY); + 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.tileInfo.segment; i++) { - for (j = 0; j <= this.tileInfo.segment; j++) { + + //从左上到右下遍历填充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 = changeElevation ? this.tileInfo.elevationInfo.elevations[(this.tileInfo.segment + 1) * i + j] : 0; + 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]); //纹理坐标 + var v = new Vertice({ + p: p, + i: index, + uv: [textureSs[j], textureTs[i]] + }); + verticeArray.push(v); + index++; } - } + } //从左上到右下填充indices //添加的点的顺序:左上->左下->右下->右上 @@ -197,23 +192,48 @@ class Tile extends MeshGraphic { * 0 3 * * 1 2*/ - for (i = 0; i < this.tileInfo.segment; i++) { - for (j = 0; j < this.tileInfo.segment; j++) { - var idx0 = (this.tileInfo.segment + 1) * i + j; - var idx1 = (this.tileInfo.segment + 1) * (i + 1) + j; + 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.concat(triangle1, triangle2); } } - var infos = { - vertices: vertices, - indices: indices, - textureCoords: textureCoords - }; + // var infos = { + // vertices: vertices, + // indices: indices, + // textureCoords: textureCoords + // }; // this.setBuffers(infos); + this.geometry = new TileGeometry(verticeArray, triangleArray); + } +} + +class Tile extends MeshGraphic { + subTiledLayer: any; + + constructor(public geometry: TileGeometry, public material: MeshTextureMaterial, public tileInfo: TileInfo) { + super(geometry, material); + } + + static getTile(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方法 From ecb14350899b475cf2515f98c29e34ca49099957 Mon Sep 17 00:00:00 2001 From: iSpring Date: Wed, 26 Oct 2016 22:37:41 +0800 Subject: [PATCH 013/109] update refactor --- src/world/TileMaterial.ts | 30 ------- src/world/geometries/TileGeometry.ts | 13 +++ src/world/graphics/MeshGraphic.ts | 9 +- src/world/graphics/Tile.ts | 12 +-- src/world/layers/SubTiledLayer.ts | 41 ++++------ src/world/materials/MeshTextureMaterial.ts | 82 ++++++++++++++----- src/world/materials/TileMaterial.ts | 24 ++++++ .../__TextureMaterial.ts} | 0 8 files changed, 122 insertions(+), 89 deletions(-) delete mode 100644 src/world/TileMaterial.ts create mode 100644 src/world/geometries/TileGeometry.ts create mode 100644 src/world/materials/TileMaterial.ts rename src/world/{TextureMaterial.ts => materials/__TextureMaterial.ts} (100%) 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/geometries/TileGeometry.ts b/src/world/geometries/TileGeometry.ts new file mode 100644 index 0000000..e9f1c3e --- /dev/null +++ b/src/world/geometries/TileGeometry.ts @@ -0,0 +1,13 @@ +/// + +import Vertice = require("./Vertice"); +import Triangle = require("./Triangle"); +import Geometry = require("./Geometry"); + +class TileGeometry extends Geometry { + constructor(public vertices: Vertice[], public triangles: Triangle[]) { + super(); + } +} + +export = TileGeometry; \ No newline at end of file diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index f9efd0c..61c6925 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -39,7 +39,14 @@ class MeshGraphic extends Graphic { this.geometry.calculateVBO(); this.geometry.calculateIBO(); this.geometry.calculateUVBO(); - this.ready = true; + } + + isGeometryReady():boolean{ + return !!this.geometry.vbo && !!this.geometry.ibo && !!this.geometry.uvbo; + } + + isReady():boolean{ + return this.isGeometryReady() && super.isReady(); } createProgram(): Program{ diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index 328e547..0a681ef 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -5,20 +5,10 @@ import Elevation = require('../Elevation'); import MathUtils = require('../math/Math'); import MeshGraphic = require('../graphics/MeshGraphic'); import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); -import Geometry = require("../geometries/Geometry"); +import TileGeometry = require("../geometries/TileGeometry"); import Vertice = require("../geometries/Vertice"); import Triangle = require("../geometries/Triangle"); -class TileGeometry extends Geometry { - constructor(public vertices: Vertice[], public triangles: Triangle[]) { - super(); - } - - // buildTriangles() { - - // } -} - class TileInfo { //type如果是GLOBE_TILE,表示其buffer已经设置为一般形式 //type如果是TERRAIN_TILE,表示其buffer已经设置为高程形式 diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index 06d424c..cb48b8b 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -3,11 +3,11 @@ import Kernel = require('../Kernel'); import Utils = require('../Utils'); import MathUtils = require('../math/Math'); import TileGrid = require('../TileGrid'); -import Object3DComponents = require('../Object3DComponents'); -import Tile = require('../Tile'); +import GraphicGroup = require('../GraphicGroup'); +import Tile = require('../graphics/Tile'); import Elevation = require('../Elevation'); -class SubTiledLayer extends Object3DComponents { +class SubTiledLayer extends GraphicGroup { level: number = -1; //该级要请求的高程数据的层级,7[8,9,10];10[11,12,13];13[14,15,16];16[17,18,19] elevationLevel = -1; @@ -33,21 +33,12 @@ class SubTiledLayer extends Object3DComponents { //重写Object3DComponents的add方法 add(tile: Tile) { - if (tile.level === this.level) { + if (tile.tileInfo.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(); @@ -59,7 +50,7 @@ class SubTiledLayer extends Object3DComponents { 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) { + if (tile.tileInfo.level === level && tile.tileInfo.row === row && tile.tileInfo.column === column) { return tile; } } @@ -91,7 +82,7 @@ class SubTiledLayer extends Object3DComponents { 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 checkResult = checkTileExist(visibleTileGrids, tile.tileInfo.level, tile.tileInfo.row, tile.tileInfo.column); var isExist = checkResult.isExist; if (isExist) { visibleTileGrids.splice(checkResult.index, 1); //已处理 @@ -121,7 +112,7 @@ class SubTiledLayer extends Object3DComponents { url: "" }; args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); - tile = new Tile(args); + tile = Tile.getTile(args.level, args.row, args.column, args.url); this.add(tile); } } @@ -129,15 +120,15 @@ class SubTiledLayer extends Object3DComponents { //如果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); - } - } + // 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 diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts index 456e325..be62153 100644 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/world/materials/MeshTextureMaterial.ts @@ -2,20 +2,21 @@ 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; url: string; - ready: boolean = false; - isDelete: boolean = false; + private ready:boolean = false; + private deleted: boolean = false; - constructor(args: any) { + constructor(imageOrUrl?: ImageType) { super(); - 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); + if(imageOrUrl){ + this.setImageOrUrl(imageOrUrl); } } @@ -23,34 +24,52 @@ class MeshTextureMaterial extends Material { return "MeshTextureMaterial"; } - isReady(){ + isReady(): boolean{ return this.ready; } + 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(); + this.onLoad(); } } setImageUrl(url: string) { - this.image = new Image(); - this.image.crossOrigin = 'anonymous';//很重要,因为图片是跨域获得的,所以一定要加上此句代码 - this.ready = false; - this.image.onload = this._onLoad.bind(this); - this.image.src = url; + 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; + } } //图片加载完成时触发 - _onLoad() { + onLoad() { //要考虑纹理已经被移除掉了图片才进入onLoad这种情况 - if (this.isDelete) { + if (this.deleted) { return; } Kernel.gl.bindTexture(Kernel.gl.TEXTURE_2D, this.texture); - //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,true); + 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); @@ -60,28 +79,47 @@ class MeshTextureMaterial extends Material { //使用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_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); } else { Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MIN_FILTER, Kernel.gl.LINEAR);//gl.NEAREST Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MAG_FILTER, Kernel.gl.LINEAR);//gl.NEAREST - } - 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.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.bindTexture(Kernel.gl.TEXTURE_2D, null); this.ready = true; } + // test(){ + // 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); + // } + //释放显卡中的texture资源 destroy() { if (Kernel.gl.isTexture(this.texture)) { Kernel.gl.deleteTexture(this.texture); } this.texture = null; - this.isDelete = true; - this.ready = false; + this.deleted = true; } } diff --git a/src/world/materials/TileMaterial.ts b/src/world/materials/TileMaterial.ts new file mode 100644 index 0000000..d67fe46 --- /dev/null +++ b/src/world/materials/TileMaterial.ts @@ -0,0 +1,24 @@ +/// +import MeshTextureMaterial= require('./MeshTextureMaterial'); +import ImageUtils = require('../Image'); + +type ImageType = HTMLImageElement | string; + +class TileMaterial extends MeshTextureMaterial{ + level: number; + + constructor(level: number, imageOrUrl: ImageType){ + super(); + this.level = level >= 0 ? level : 20; + this.setImageOrUrl(imageOrUrl); + } + + 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/TextureMaterial.ts b/src/world/materials/__TextureMaterial.ts similarity index 100% rename from src/world/TextureMaterial.ts rename to src/world/materials/__TextureMaterial.ts From 66a4e8b668b39f6b0a21fb50acc73c7e66df5b40 Mon Sep 17 00:00:00 2001 From: iSpring Date: Wed, 26 Oct 2016 23:29:42 +0800 Subject: [PATCH 014/109] update refactor --- src/world/Globe.ts | 12 +- src/world/Object3D.ts | 1 - src/world/Object3DComponents.ts | 140 -------------------- src/world/ProgramUtils.ts | 2 +- src/world/Renderer.ts | 142 ++++++++++----------- src/world/Scene.ts | 4 +- src/world/geometries/TileGeometry.ts | 2 +- src/world/graphics/MeshGraphic.ts | 40 +++--- src/world/graphics/Tile.ts | 23 +--- src/world/layers/SubTiledLayer.ts | 4 +- src/world/layers/TiledLayer.ts | 4 +- src/world/materials/MeshTextureMaterial.ts | 3 +- src/world/materials/TileMaterial.ts | 2 +- src/world/materials/__TextureMaterial.ts | 4 +- 14 files changed, 113 insertions(+), 270 deletions(-) delete mode 100644 src/world/Object3DComponents.ts diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 3e4488a..39ea305 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -1,13 +1,13 @@ /// import Kernel = require("./Kernel"); import Utils = require("./Utils"); -import ShaderContent = require("./ShaderContent"); +// import ShaderContent = require("./ShaderContent"); import Renderer = require("./Renderer"); import PerspectiveCamera = require("./PerspectiveCamera"); import Scene = require("./Scene"); import TiledLayer = require("./layers/TiledLayer"); import SubTiledLayer = require("./layers/SubTiledLayer"); -import Tile = require("./Tile"); +import Tile = require("./graphics/Tile"); import ImageUtils = require("./Image"); import EventUtils = require("./Event"); @@ -25,9 +25,9 @@ class Globe { args = args || {}; 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); + // var vs_content = ShaderContent.SIMPLE_SHADER.VS_CONTENT; + // var fs_content = ShaderContent.SIMPLE_SHADER.FS_CONTENT; + this.renderer = Kernel.renderer = new Renderer(canvas); this.scene = new Scene(); var radio = canvas.width / canvas.height; this.camera = new PerspectiveCamera(30, radio, 1.0, 20000000.0); @@ -72,7 +72,7 @@ class Globe { url: "" }; args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); - var tile = new Tile(args); + var tile = Tile.getTile(args.level, args.row, args.column, args.url); subLayer1.add(tile); } } diff --git a/src/world/Object3D.ts b/src/world/Object3D.ts index 2b818e2..9f64aa8 100644 --- a/src/world/Object3D.ts +++ b/src/world/Object3D.ts @@ -3,7 +3,6 @@ import Kernel = require('./Kernel'); import Matrix = require('./math/Matrix'); import Vertice = require('./math/Vertice'); import Vector = require('./math/Vector'); -import TextureMaterial = require('./TextureMaterial'); class Object3D { matrix: Matrix; diff --git a/src/world/Object3DComponents.ts b/src/world/Object3DComponents.ts deleted file mode 100644 index 7470212..0000000 --- a/src/world/Object3DComponents.ts +++ /dev/null @@ -1,140 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Vector = require('./math/Vector'); -import Matrix = require('./math/Matrix'); -import Object3D = require('./Object3D'); -import Graphic = require('./graphics/Graphic'); -import PerspectiveCamera = require('./PerspectiveCamera'); - -// type ChildType = Object3D | Object3DComponents; - -//三维对象集合 -class Object3DComponents { - id: number; - matrix: Matrix; - visible: boolean; - parent: any; - children: Graphic[]; - - constructor() { - this.id = ++Kernel.idCounter; - this.matrix = new Matrix(); - this.visible = true; - this.parent = null; - this.children = []; - } - - add(obj: Graphic) { - if (this.findObjById(obj.id) !== null) { - console.debug("obj已经存在于Object3DComponents中,无法将其再次加入!"); - return; - } else { - this.children.push(obj); - obj.parent = this; - } - } - - remove(obj: Graphic) { - 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/ProgramUtils.ts b/src/world/ProgramUtils.ts index a93f62b..5c74fd4 100644 --- a/src/world/ProgramUtils.ts +++ b/src/world/ProgramUtils.ts @@ -23,7 +23,7 @@ const ProgramUtils = { if(!program){ program = graphic.createProgram(); - this.programs.push(program); + programs.push(program); } return program; diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 317b9fb..9c6e599 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -10,13 +10,7 @@ class Renderer { camera: PerspectiveCamera = null; bAutoRefresh: boolean = false; - constructor(canvas: HTMLCanvasElement, vertexShaderText: string, fragmentShaderText: string) { - if (!(vertexShaderText !== "")) { - throw "invalid vertexShaderText"; - } - if (!(fragmentShaderText !== "")) { - throw "invalid fragmentShaderText"; - } + constructor(canvas: HTMLCanvasElement) { //之所以在此处设置Kernel.renderer是因为要在tick函数中使用 Kernel.renderer = this; @@ -40,71 +34,71 @@ 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); - } + // 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); @@ -114,7 +108,7 @@ class Renderer { return; } - initShaders(vertexShaderText, fragmentShaderText); + //initShaders(vertexShaderText, fragmentShaderText); gl.clearColor(255, 255, 255, 1.0); //gl.enable(gl.DEPTH_TEST); @@ -132,7 +126,9 @@ class Renderer { 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; + //update viewMatrix and projViewMatrix of camera camera.viewMatrix = camera.getViewMatrix(); + camera.projViewMatrix = camera.projMatrix.multiplyMatrix(camera.viewMatrix); scene.draw(camera); } diff --git a/src/world/Scene.ts b/src/world/Scene.ts index ad1d7d5..f7be677 100644 --- a/src/world/Scene.ts +++ b/src/world/Scene.ts @@ -1,8 +1,8 @@ /// -import Object3DComponents = require('./Object3DComponents'); +import GraphicGroup = require('./GraphicGroup'); import TiledLayer = require("./layers/TiledLayer"); -class Scene extends Object3DComponents{ +class Scene extends GraphicGroup{ tiledLayer: TiledLayer; } diff --git a/src/world/geometries/TileGeometry.ts b/src/world/geometries/TileGeometry.ts index e9f1c3e..19ba3cf 100644 --- a/src/world/geometries/TileGeometry.ts +++ b/src/world/geometries/TileGeometry.ts @@ -1,4 +1,4 @@ -/// +/// import Vertice = require("./Vertice"); import Triangle = require("./Triangle"); diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index 61c6925..bac0102 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -8,29 +8,29 @@ import MeshTextureMaterial = require("../materials/MeshTextureMaterial"); import PerspectiveCamera = require("../PerspectiveCamera"); 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;', -'}' +` +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));', - '}' +` +precision mediump float; +varying vec2 vUV; +uniform sampler2D uSampler; + +void main() +{ + gl_FragColor = texture2D(uSampler, vec2(vUV.s, vUV.t)); +} `; class MeshGraphic extends Graphic { diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index 0a681ef..ee60b25 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -1,10 +1,10 @@ -/// +/// import Kernel = require('../Kernel'); import Enum = require('../Enum'); import Elevation = require('../Elevation'); import MathUtils = require('../math/Math'); import MeshGraphic = require('../graphics/MeshGraphic'); -import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); +import TileMaterial = require('../materials/TileMaterial'); import TileGeometry = require("../geometries/TileGeometry"); import Vertice = require("../geometries/Vertice"); import Triangle = require("../geometries/Triangle"); @@ -26,14 +26,13 @@ class TileInfo { segment: number = 1; elevationInfo: any = null; geometry: TileGeometry; - material: MeshTextureMaterial; + material: TileMaterial; visible: boolean; constructor(public level: number, public row: number, public column: number, public url: string) { this._setTileInfo(); this._checkTerrain(); - this._createGeometry(); - this._createMaterial(); + this.material = new TileMaterial(this.level, this.url); } // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 @@ -56,18 +55,6 @@ class TileInfo { this.maxY = maxCoord[1]; } - _createGeometry() { - this.geometry = null; - } - - _createMaterial() { - var matArgs = { - level: this.level, - url: this.url - }; - this.material = new MeshTextureMaterial(matArgs); - } - /** * 判断是否满足现实Terrain的条件,若满足则转换为三维地形 * 条件: @@ -213,7 +200,7 @@ class TileInfo { class Tile extends MeshGraphic { subTiledLayer: any; - constructor(public geometry: TileGeometry, public material: MeshTextureMaterial, public tileInfo: TileInfo) { + constructor(public geometry: TileGeometry, public material: TileMaterial, public tileInfo: TileInfo) { super(geometry, material); } diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index cb48b8b..03b694a 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -140,7 +140,7 @@ class SubTiledLayer extends GraphicGroup { 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); + var tileGrid = TileGrid.getTileGridAncestor(this.elevationLevel, tile.tileInfo.level, tile.tileInfo.row, tile.tileInfo.column); name = tileGrid.level + "_" + tileGrid.row + "_" + tileGrid.column; if (result.indexOf(name) < 0) { result.push(name); @@ -165,7 +165,7 @@ class SubTiledLayer extends GraphicGroup { for (var i = 0; i < this.children.length; i++) { var tile = this.children[i]; if (tile) { - var isTileLoaded = tile.material.loaded; + var isTileLoaded = tile.material.isReady(); if (!isTileLoaded) { return false; } diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index cc09fc6..e4ef093 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -1,9 +1,9 @@ /// import Kernel = require('../Kernel'); -import Object3DComponents = require('../Object3DComponents'); +import GraphicGroup = require('../GraphicGroup'); import SubTiledLayer = require('./SubTiledLayer'); -abstract class TiledLayer extends Object3DComponents { +abstract class TiledLayer extends GraphicGroup { //重写 add(subTiledLayer: SubTiledLayer) { super.add(subTiledLayer); diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts index be62153..5207bcf 100644 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/world/materials/MeshTextureMaterial.ts @@ -15,6 +15,7 @@ class MeshTextureMaterial extends Material { constructor(imageOrUrl?: ImageType) { super(); + this.texture = Kernel.gl.createTexture(); if(imageOrUrl){ this.setImageOrUrl(imageOrUrl); } @@ -73,7 +74,7 @@ class MeshTextureMaterial extends Material { Kernel.gl.texImage2D(Kernel.gl.TEXTURE_2D, 0, Kernel.gl.RGBA, Kernel.gl.RGBA, Kernel.gl.UNSIGNED_BYTE, this.image); - var isMipMap = this.image.width === this.image.height && MathUtils.isPowerOfTwo(this.image.width); + var isMipMap = false;//this.image.width === this.image.height && MathUtils.isPowerOfTwo(this.image.width); if (isMipMap) { //使用MipMap diff --git a/src/world/materials/TileMaterial.ts b/src/world/materials/TileMaterial.ts index d67fe46..11579e3 100644 --- a/src/world/materials/TileMaterial.ts +++ b/src/world/materials/TileMaterial.ts @@ -1,4 +1,4 @@ -/// +/// import MeshTextureMaterial= require('./MeshTextureMaterial'); import ImageUtils = require('../Image'); diff --git a/src/world/materials/__TextureMaterial.ts b/src/world/materials/__TextureMaterial.ts index 308108e..8b410c5 100644 --- a/src/world/materials/__TextureMaterial.ts +++ b/src/world/materials/__TextureMaterial.ts @@ -1,5 +1,5 @@ -/// -import Kernel = require('./Kernel'); +/// +import Kernel = require('../Kernel'); class TextureMaterial { texture: WebGLTexture = null; From 19bc21d8a8555d9f1afd15e7912a68f3cef0e523 Mon Sep 17 00:00:00 2001 From: iSpring Date: Wed, 26 Oct 2016 23:50:30 +0800 Subject: [PATCH 015/109] update --- src/world/graphics/Tile.ts | 2 +- src/world/materials/MeshTextureMaterial.ts | 37 +++++++----- src/world/materials/__TextureMaterial.ts | 68 ---------------------- 3 files changed, 23 insertions(+), 84 deletions(-) delete mode 100644 src/world/materials/__TextureMaterial.ts diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index ee60b25..c2c4ae3 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -183,7 +183,7 @@ class TileInfo { var v3: Vertice = verticeArray[idx3]; var triangle1 = new Triangle(v0, v1, v2); var triangle2 = new Triangle(v2, v3, v0); - triangleArray.concat(triangle1, triangle2); + triangleArray.push(triangle1, triangle2); } } diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts index 5207bcf..2c922ee 100644 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/world/materials/MeshTextureMaterial.ts @@ -74,24 +74,31 @@ class MeshTextureMaterial extends Material { Kernel.gl.texImage2D(Kernel.gl.TEXTURE_2D, 0, Kernel.gl.RGBA, Kernel.gl.RGBA, Kernel.gl.UNSIGNED_BYTE, this.image); - var isMipMap = false;//this.image.width === this.image.height && MathUtils.isPowerOfTwo(this.image.width); + // var isMipMap = this.image.width === this.image.height && MathUtils.isPowerOfTwo(this.image.width); - if (isMipMap) { - //使用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_MIPMAP_NEAREST);//LINEAR_MIPMAP_LINEAR + // if (isMipMap) { + // //使用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_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.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); - } else { - Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MIN_FILTER, Kernel.gl.LINEAR);//gl.NEAREST - Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MAG_FILTER, Kernel.gl.LINEAR);//gl.NEAREST - - 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); + // } else { + // Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MIN_FILTER, Kernel.gl.LINEAR);//gl.NEAREST + // Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MAG_FILTER, Kernel.gl.LINEAR);//gl.NEAREST + + // 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); + // } + + //使用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); diff --git a/src/world/materials/__TextureMaterial.ts b/src/world/materials/__TextureMaterial.ts deleted file mode 100644 index 8b410c5..0000000 --- a/src/world/materials/__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 From 3c54187e2a200a040c88899754d211b50419eafb Mon Sep 17 00:00:00 2001 From: iSpring Date: Thu, 27 Oct 2016 21:00:01 +0800 Subject: [PATCH 016/109] remove ShaderContent.ts --- src/world/Renderer.ts | 68 ---------------------- src/world/ShaderContent.ts | 48 --------------- src/world/materials/MeshTextureMaterial.ts | 57 +++++------------- 3 files changed, 16 insertions(+), 157 deletions(-) delete mode 100644 src/world/ShaderContent.ts diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 9c6e599..bd18198 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -34,72 +34,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) { @@ -108,8 +42,6 @@ class Renderer { return; } - //initShaders(vertexShaderText, fragmentShaderText); - gl.clearColor(255, 255, 255, 1.0); //gl.enable(gl.DEPTH_TEST); gl.disable(gl.DEPTH_TEST); //此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 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/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts index 2c922ee..c02ec5a 100644 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/world/materials/MeshTextureMaterial.ts @@ -74,53 +74,28 @@ class MeshTextureMaterial extends Material { Kernel.gl.texImage2D(Kernel.gl.TEXTURE_2D, 0, Kernel.gl.RGBA, Kernel.gl.RGBA, Kernel.gl.UNSIGNED_BYTE, this.image); - // var isMipMap = this.image.width === this.image.height && MathUtils.isPowerOfTwo(this.image.width); - - // if (isMipMap) { - // //使用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_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); - // } else { - // Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MIN_FILTER, Kernel.gl.LINEAR);//gl.NEAREST - // Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MAG_FILTER, Kernel.gl.LINEAR);//gl.NEAREST - - // 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); - // } - - //使用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); + var isMipMap = this.image.width === this.image.height && MathUtils.isPowerOfTwo(this.image.width); + + if (isMipMap) { + //使用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); + } else { + Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MIN_FILTER, Kernel.gl.LINEAR);//gl.NEAREST + Kernel.gl.texParameteri(Kernel.gl.TEXTURE_2D, Kernel.gl.TEXTURE_MAG_FILTER, Kernel.gl.LINEAR);//gl.NEAREST + + 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.bindTexture(Kernel.gl.TEXTURE_2D, null); this.ready = true; } - // test(){ - // 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); - // } - //释放显卡中的texture资源 destroy() { if (Kernel.gl.isTexture(this.texture)) { From 844ad0b8af8d1a3f284dbf5fb9e0ff6fe687625e Mon Sep 17 00:00:00 2001 From: iSpring Date: Thu, 27 Oct 2016 22:30:37 +0800 Subject: [PATCH 017/109] fix the blank issue when zoom into level 14,#9 --- src/world/Event.ts | 10 ++++------ src/world/GraphicGroup.ts | 5 ++++- src/world/math/Matrix.ts | 38 ++++++++++++++++++++++---------------- src/world/math/Vector.ts | 15 +++++++++++++++ 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/world/Event.ts b/src/world/Event.ts index f336fab..359d451 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -56,7 +56,7 @@ const EventModule = { 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] + "]"); + //console.log("单击点三维坐标:(" + pickResult[0].x + "," + pickResult[0].y + "," + pickResult[0].z + ");经纬度坐标:[" + this.dragGeo[0] + "," + this.dragGeo[1] + "]"); } this.canvas.addEventListener("mousemove", this.onMouseMoveListener, false); } @@ -98,12 +98,10 @@ const EventModule = { } 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 rotateRadian = -Vector.getRadianOfTwoVectors(v1, v2); var camera: PerspectiveCamera = Kernel.globe.camera; camera.worldRotateByVector(rotateRadian, rotateVector); }, @@ -156,8 +154,8 @@ const EventModule = { } var newLevel = globe.CURRENT_LEVEL + deltaLevel; if(newLevel >= 0){ - //globe.setLevel(newLevel); - globe.animateToLevel(newLevel); + globe.setLevel(newLevel); + //globe.animateToLevel(newLevel); } }, diff --git a/src/world/GraphicGroup.ts b/src/world/GraphicGroup.ts index 0417d68..c2cc456 100644 --- a/src/world/GraphicGroup.ts +++ b/src/world/GraphicGroup.ts @@ -21,13 +21,16 @@ class GraphicGroup{ g.parent = this; } - remove(g: Drawable){ + 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(){ diff --git a/src/world/math/Matrix.ts b/src/world/math/Matrix.ts index 7b87858..63d8274 100644 --- a/src/world/math/Matrix.ts +++ b/src/world/math/Matrix.ts @@ -132,21 +132,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 +163,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; @@ -275,6 +275,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"; diff --git a/src/world/math/Vector.ts b/src/world/math/Vector.ts index fb2687d..3bae7bf 100644 --- a/src/world/math/Vector.ts +++ b/src/world/math/Vector.ts @@ -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); } From 0375947e4eb8ad126ebefa4ced92162e9486a46d Mon Sep 17 00:00:00 2001 From: iSpring Date: Thu, 27 Oct 2016 22:31:14 +0800 Subject: [PATCH 018/109] update --- index-bundle.html | 2 +- index-src.html | 2 +- src/world/Event.ts | 4 ++-- src/world/PerspectiveCamera.ts | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/index-bundle.html b/index-bundle.html index 7e5daab..ce0ce02 100644 --- a/index-bundle.html +++ b/index-bundle.html @@ -13,8 +13,8 @@ - + diff --git a/src/world/Event.ts b/src/world/Event.ts index 359d451..6288c43 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -154,8 +154,8 @@ const EventModule = { } var newLevel = globe.CURRENT_LEVEL + deltaLevel; if(newLevel >= 0){ - globe.setLevel(newLevel); - //globe.animateToLevel(newLevel); + //globe.setLevel(newLevel); + globe.animateToLevel(newLevel); } }, diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index 858e646..d55d6b7 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -9,9 +9,9 @@ import Plan = require('./math/Plan'); import TileGrid = require('./TileGrid'); import Matrix = require('./math/Matrix'); import Object3D = require('./Object3D'); -import Globe = require('./Globe');//just used for TypeScript validate type class PerspectiveCamera extends Object3D { + private animationDuration = 600;//层级变化的动画周期是600毫秒 pitch: number; viewMatrix: Matrix; projMatrix: Matrix; @@ -284,7 +284,7 @@ class PerspectiveCamera extends Object3D { animateToLevel(level: number): void { var newMat = this._animateToLevel(level); this._animateToMatrix(newMat, () => { - (Kernel.globe as Globe).CURRENT_LEVEL = level; + Kernel.globe.CURRENT_LEVEL = level; }); } @@ -295,7 +295,7 @@ class PerspectiveCamera extends Object3D { this.animating = true; var oldPosition = this.getPosition(); var newPosition = newMat.getPosition(); - var span = 1000; + var span = this.animationDuration; var singleSpan = 1000 / 60; var count = Math.floor(span / singleSpan); var deltaX = (newPosition.x - oldPosition.x) / count; From db7d0a1af2beb25b2b9c36d52100bf5025186c87 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 28 Oct 2016 10:53:47 +0800 Subject: [PATCH 019/109] remove deprecated folder --- deprecated/WEB-INF/web.xml | 11 ---------- deprecated/proxy.jsp | 42 -------------------------------------- 2 files changed, 53 deletions(-) delete mode 100644 deprecated/WEB-INF/web.xml delete mode 100644 deprecated/proxy.jsp 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); - } -%> From 8fc7014eddb78b386498621a756251a99e1b61e7 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 28 Oct 2016 13:12:58 +0800 Subject: [PATCH 020/109] update MAX_LEVEL from 15 to 14 --- src/world/Globe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 39ea305..56b1199 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -12,7 +12,7 @@ import ImageUtils = require("./Image"); import EventUtils = require("./Event"); class Globe { - MAX_LEVEL: number = 15;//最大的渲染级别15 + MAX_LEVEL: number = 14;//最大的渲染级别14 CURRENT_LEVEL: number = -1; //当前渲染等级 REFRESH_INTERVAL: number = 300; //Globe自动刷新时间间隔,以毫秒为单位 idTimeOut: any = null; //refresh自定刷新的timeOut的handle From ee741512764c097ad901109d06c540b29c46fd8b Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 28 Oct 2016 14:40:05 +0800 Subject: [PATCH 021/109] update version from 0.1.1 to 0.2.1 --- package.json | 2 +- versions.txt | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4e5843e..2f2d695 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webglobe", - "version": "0.1.1", + "version": "0.2.1", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { diff --git a/versions.txt b/versions.txt index fa925e1..ac6ca8e 100644 --- a/versions.txt +++ b/versions.txt @@ -84,4 +84,8 @@ 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 \ No newline at end of file From fa0ae53f82ad2a607f165eedf6c00369795e04b4 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 4 Nov 2016 14:47:19 +0800 Subject: [PATCH 022/109] update SosoTiledLayer --- src/world/layers/SosoTiledLayer.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/world/layers/SosoTiledLayer.ts b/src/world/layers/SosoTiledLayer.ts index a27eee9..57e8edb 100644 --- a/src/world/layers/SosoTiledLayer.ts +++ b/src/world/layers/SosoTiledLayer.ts @@ -3,6 +3,15 @@ import TiledLayer = require('./TiledLayer'); class SosoTiledLayer extends TiledLayer { getImageUrl(level: number, row: number, column: number): string { + // if(level >= 10){ + // return this.getImageUrl2(level, row, column); + // } + + return this.getImageUrl1(level, row, column); + } + + getImageUrl1(level: number, row: number, column: number): string { + //影像 var url = ""; var tileCount = Math.pow(2, level); var a = column; @@ -11,11 +20,21 @@ class SosoTiledLayer extends TiledLayer { 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"; + var sateUrl = "//p" + serverIdx + ".map.soso.com/sateTiles/" + level + "/" + A + "/" + B + "/" + a + "_" + b + ".jpg"; url = sateUrl; return url; } + + getImageUrl2(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 From 2d5f76734ff8739679bebcdd5f9add3c460e02db Mon Sep 17 00:00:00 2001 From: iSpring Date: Mon, 7 Nov 2016 21:12:47 +0800 Subject: [PATCH 023/109] remove elevation related code --- src/world/Elevation.ts | 308 ------------------------------ src/world/Globe.ts | 30 +-- src/world/Kernel.ts | 4 - src/world/graphics/Tile.ts | 59 +----- src/world/layers/SubTiledLayer.ts | 52 +---- versions.txt | 4 +- 6 files changed, 16 insertions(+), 441 deletions(-) delete mode 100644 src/world/Elevation.ts diff --git a/src/world/Elevation.ts b/src/world/Elevation.ts deleted file mode 100644 index 2756bf1..0000000 --- a/src/world/Elevation.ts +++ /dev/null @@ -1,308 +0,0 @@ -/// -import Kernel = require('./Kernel'); -import Utils = require('./Utils'); -import MathUtils = require('./math/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/Globe.ts b/src/world/Globe.ts index 56b1199..2baddda 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -1,7 +1,6 @@ /// import Kernel = require("./Kernel"); import Utils = require("./Utils"); -// import ShaderContent = require("./ShaderContent"); import Renderer = require("./Renderer"); import PerspectiveCamera = require("./PerspectiveCamera"); import Scene = require("./Scene"); @@ -101,7 +100,10 @@ class Globe { animateToLevel(level: number){ if(!this.isAnimating()){ - this.camera.animateToLevel(level); + level = level > this.MAX_LEVEL ? this.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 + if(level !== this.CURRENT_LEVEL){ + this.camera.animateToLevel(level); + } } } @@ -167,30 +169,8 @@ class Globe { 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(); - } - } - } } + } export = Globe; \ No newline at end of file diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index 6eecd2c..e408947 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -12,10 +12,6 @@ const Kernel = { 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: "" }; diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index c2c4ae3..83fe0ed 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -1,7 +1,6 @@ /// import Kernel = require('../Kernel'); import Enum = require('../Enum'); -import Elevation = require('../Elevation'); import MathUtils = require('../math/Math'); import MeshGraphic = require('../graphics/MeshGraphic'); import TileMaterial = require('../materials/TileMaterial'); @@ -14,7 +13,6 @@ class TileInfo { //type如果是TERRAIN_TILE,表示其buffer已经设置为高程形式 //type如果是UNKNOWN,表示buffer没设置 type: number = Enum.UNKNOWN; - elevationLevel: number = 0;//高程level minLon: number = null; minLat: number = null; maxLon: number = null; @@ -31,14 +29,15 @@ class TileInfo { constructor(public level: number, public row: number, public column: number, public url: string) { this._setTileInfo(); - this._checkTerrain(); + if (this.type == Enum.UNKNOWN) { + //初始type为UNKNOWN,还未初始化buffer,应该显示为GlobeTile + this._handleGlobeTile(); + } this.material = new TileMaterial(this.level, this.url); } // 根据传入的切片的层级以及行列号信息设置切片的经纬度范围 以及设置其纹理 _setTileInfo() { - this.elevationLevel = Elevation.getAncestorElevationLevel(this.level); - //经纬度范围 var Egeo = MathUtils.getTileGeographicEnvelopByGrid(this.level, this.row, this.column); this.minLon = Egeo.minLon; @@ -55,41 +54,6 @@ class TileInfo { this.maxY = maxCoord[1]; } - /** - * 判断是否满足现实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); - } - 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; @@ -102,13 +66,6 @@ class TileInfo { this._handleTile(); } - //处理地形的切片 - _handleTerrainTile() { - this.type = Enum.TERRAIN_TILE; - this.segment = 10; - this._handleTile(); - } - //如果是GlobeTile,那么elevations为null //如果是TerrainTile,那么elevations是一个一维数组,大小是(segment+1)*(segment+1) _handleTile() { @@ -147,7 +104,7 @@ class TileInfo { 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 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); //顶点坐标 @@ -187,12 +144,6 @@ class TileInfo { } } - // var infos = { - // vertices: vertices, - // indices: indices, - // textureCoords: textureCoords - // }; - // this.setBuffers(infos); this.geometry = new TileGeometry(verticeArray, triangleArray); } } diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index 03b694a..a4fe7ed 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -5,29 +5,26 @@ import MathUtils = require('../math/Math'); import TileGrid = require('../TileGrid'); import GraphicGroup = require('../GraphicGroup'); import Tile = require('../graphics/Tile'); -import Elevation = require('../Elevation'); class SubTiledLayer extends GraphicGroup { 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) { + /*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); - } + }*/ + Kernel.gl.disable(Kernel.gl.DEPTH_TEST); super.draw(camera); } @@ -118,49 +115,6 @@ class SubTiledLayer extends GraphicGroup { } } - //如果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.tileInfo.level, tile.tileInfo.row, tile.tileInfo.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]; diff --git a/versions.txt b/versions.txt index ac6ca8e..c4bde54 100644 --- a/versions.txt +++ b/versions.txt @@ -88,4 +88,6 @@ 0.2 将World.js中的许多类的设计理念,包括Program、GraphicGroup、Graphic、Geometry、Material、VertexBufferObject等,现在SubTiledLayer和TiledLayer都继承自GraphicGroup,并且让Tile继承自于Graphic -0.2.1 修复了在leve 15及以上的情况下,拖动地图导致地图空白的问题,见issue#9 \ No newline at end of file +0.2.1 修复了在leve 15及以上的情况下,拖动地图导致地图空白的问题,见issue#9 + +0.2.2 删除高程相关代码 \ No newline at end of file From fd9ec1f78799e07c6ec60146aad28b2be2716c24 Mon Sep 17 00:00:00 2001 From: iSpring Date: Mon, 7 Nov 2016 21:14:37 +0800 Subject: [PATCH 024/109] update versions.txt --- versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.txt b/versions.txt index c4bde54..4851f98 100644 --- a/versions.txt +++ b/versions.txt @@ -86,7 +86,7 @@ 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 引入World.js中的许多类的设计理念,包括Program、GraphicGroup、Graphic、Geometry、Material、VertexBufferObject等,现在SubTiledLayer和TiledLayer都继承自GraphicGroup,并且让Tile继承自于Graphic 0.2.1 修复了在leve 15及以上的情况下,拖动地图导致地图空白的问题,见issue#9 From b5404eb38bf00fa2d033b7cd62ea0c16649b04fd Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 17 Nov 2016 18:13:07 +0800 Subject: [PATCH 025/109] rename Geometry to Mesh --- src/world/geometries/Box.ts | 4 ++-- src/world/geometries/{Geometry.ts => Mesh.ts} | 6 +++--- src/world/geometries/TileGeometry.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/world/geometries/{Geometry.ts => Mesh.ts} (97%) diff --git a/src/world/geometries/Box.ts b/src/world/geometries/Box.ts index 184b50e..324d155 100644 --- a/src/world/geometries/Box.ts +++ b/src/world/geometries/Box.ts @@ -2,9 +2,9 @@ import Vertice = require("./Vertice"); import Triangle = require("./Triangle"); -import Geometry = require("./Geometry"); +import Mesh = require("./Mesh"); -class Box extends Geometry { +class Box extends Mesh { constructor(public length: number, public width: number, public height: number) { super(); this.buildTriangles(); diff --git a/src/world/geometries/Geometry.ts b/src/world/geometries/Mesh.ts similarity index 97% rename from src/world/geometries/Geometry.ts rename to src/world/geometries/Mesh.ts index efdf4f5..09693be 100644 --- a/src/world/geometries/Geometry.ts +++ b/src/world/geometries/Mesh.ts @@ -1,11 +1,11 @@ -/// +/// import Kernel = require("../Kernel"); import Vertice = require("./Vertice"); import Triangle = require("./Triangle"); import Object3D = require("../Object3D"); import VertexBufferObject = require("../VertexBufferObject"); -class Geometry extends Object3D { +class Mesh extends Object3D { vertices: Vertice[]; triangles: Triangle[]; vbo: VertexBufferObject; @@ -151,4 +151,4 @@ class Geometry extends Object3D { } } -export = Geometry; \ No newline at end of file +export = Mesh; \ No newline at end of file diff --git a/src/world/geometries/TileGeometry.ts b/src/world/geometries/TileGeometry.ts index 19ba3cf..3626b48 100644 --- a/src/world/geometries/TileGeometry.ts +++ b/src/world/geometries/TileGeometry.ts @@ -2,9 +2,9 @@ import Vertice = require("./Vertice"); import Triangle = require("./Triangle"); -import Geometry = require("./Geometry"); +import Mesh = require("./Mesh"); -class TileGeometry extends Geometry { +class TileGeometry extends Mesh { constructor(public vertices: Vertice[], public triangles: Triangle[]) { super(); } From 6f3088926481d70359bc18fd469ef4d0b26932aa Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 17 Nov 2016 18:32:46 +0800 Subject: [PATCH 026/109] rename Geometry to Mesh and add npm scripts --- README.md | 27 ++++++++++++++------------- package.json | 6 +++++- src/world/geometries/Geometry.ts | 7 +++++++ src/world/graphics/MeshGraphic.ts | 4 ++-- versions.txt | 4 +++- 5 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 src/world/geometries/Geometry.ts diff --git a/README.md b/README.md index 92dac3f..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等。 @@ -18,19 +18,20 @@ Demo: https://ispring.github.io/WebGlobe/index.html ## Setup dev environment 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,且是默认的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/package.json b/package.json index 2f2d695..cfb2469 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,11 @@ "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/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/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index bac0102..d9ced79 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -3,7 +3,7 @@ import Kernel = require("../Kernel"); import Program = require("../Program"); import Graphic = require("./Graphic"); -import Geometry = require("../geometries/Geometry"); +import Mesh = require("../geometries/Mesh"); import MeshTextureMaterial = require("../materials/MeshTextureMaterial"); import PerspectiveCamera = require("../PerspectiveCamera"); @@ -34,7 +34,7 @@ void main() `; class MeshGraphic extends Graphic { - constructor(public geometry: Geometry, public material: MeshTextureMaterial){ + constructor(public geometry: Mesh, public material: MeshTextureMaterial){ super(geometry, material); this.geometry.calculateVBO(); this.geometry.calculateIBO(); diff --git a/versions.txt b/versions.txt index 4851f98..ddff7c5 100644 --- a/versions.txt +++ b/versions.txt @@ -90,4 +90,6 @@ 0.2.1 修复了在leve 15及以上的情况下,拖动地图导致地图空白的问题,见issue#9 -0.2.2 删除高程相关代码 \ No newline at end of file +0.2.2 删除高程相关代码 + +0.2.3 将world/geometries/Geomtry重命名为world/geometries/Mesh,并在package.json中添加了多个npm scripts,对应gulp中定义的tasks \ No newline at end of file From e12922d15be1c89f0401ccc3de329050bb4df545 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 17 Nov 2016 18:34:28 +0800 Subject: [PATCH 027/109] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cfb2469..11b1239 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webglobe", - "version": "0.2.1", + "version": "0.2.3", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { From 352809084d3438bbbaff87dbaf733bbb862b7d14 Mon Sep 17 00:00:00 2001 From: iSpring Date: Thu, 17 Nov 2016 21:08:15 +0800 Subject: [PATCH 028/109] update console.log() of SubTiledLayer --- src/world/layers/SubTiledLayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index a4fe7ed..3c4b8e8 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -94,7 +94,7 @@ class SubTiledLayer extends GraphicGroup { var b = this.remove(tilesNeedDelete[0]); tilesNeedDelete.splice(0, 1); if (!b) { - console.debug("LINE:2191,subTiledLayer.remove(tilesNeedDelete[0])失败"); + console.debug("subTiledLayer.remove(tilesNeedDelete[0])失败"); } } From d6700029287ac49fc8afe091f7601113f31beb4d Mon Sep 17 00:00:00 2001 From: iSpring Date: Thu, 17 Nov 2016 22:19:12 +0800 Subject: [PATCH 029/109] rename world/geometries/Vertice to world/geometries/MeshVertice --- src/world/geometries/Box.ts | 2 +- src/world/geometries/Mesh.ts | 2 +- src/world/geometries/{Vertice.ts => MeshVertice.ts} | 6 +++--- src/world/geometries/TileGeometry.ts | 2 +- src/world/geometries/Triangle.ts | 2 +- src/world/graphics/Marker.ts | 13 +++++++++++++ src/world/graphics/Tile.ts | 2 +- 7 files changed, 21 insertions(+), 8 deletions(-) rename src/world/geometries/{Vertice.ts => MeshVertice.ts} (70%) create mode 100644 src/world/graphics/Marker.ts diff --git a/src/world/geometries/Box.ts b/src/world/geometries/Box.ts index 324d155..02bbd2b 100644 --- a/src/world/geometries/Box.ts +++ b/src/world/geometries/Box.ts @@ -1,6 +1,6 @@ /// -import Vertice = require("./Vertice"); +import Vertice = require("./MeshVertice"); import Triangle = require("./Triangle"); import Mesh = require("./Mesh"); diff --git a/src/world/geometries/Mesh.ts b/src/world/geometries/Mesh.ts index 09693be..0c0dd4a 100644 --- a/src/world/geometries/Mesh.ts +++ b/src/world/geometries/Mesh.ts @@ -1,6 +1,6 @@ /// import Kernel = require("../Kernel"); -import Vertice = require("./Vertice"); +import Vertice = require("./MeshVertice"); import Triangle = require("./Triangle"); import Object3D = require("../Object3D"); import VertexBufferObject = require("../VertexBufferObject"); diff --git a/src/world/geometries/Vertice.ts b/src/world/geometries/MeshVertice.ts similarity index 70% rename from src/world/geometries/Vertice.ts rename to src/world/geometries/MeshVertice.ts index 430f13a..e368413 100644 --- a/src/world/geometries/Vertice.ts +++ b/src/world/geometries/MeshVertice.ts @@ -1,5 +1,5 @@ -/// -class Vertice{ +/// +class MeshVertice{ p:number[]; n:number[]; uv:number[]; @@ -15,4 +15,4 @@ class Vertice{ } } -export = Vertice; \ No newline at end of file +export = MeshVertice; \ No newline at end of file diff --git a/src/world/geometries/TileGeometry.ts b/src/world/geometries/TileGeometry.ts index 3626b48..37f3961 100644 --- a/src/world/geometries/TileGeometry.ts +++ b/src/world/geometries/TileGeometry.ts @@ -1,6 +1,6 @@ /// -import Vertice = require("./Vertice"); +import Vertice = require("./MeshVertice"); import Triangle = require("./Triangle"); import Mesh = require("./Mesh"); diff --git a/src/world/geometries/Triangle.ts b/src/world/geometries/Triangle.ts index dd0ef14..3d04629 100644 --- a/src/world/geometries/Triangle.ts +++ b/src/world/geometries/Triangle.ts @@ -1,5 +1,5 @@ /// -import Vertice = require("./Vertice"); +import Vertice = require("./MeshVertice"); class Triangle{ diff --git a/src/world/graphics/Marker.ts b/src/world/graphics/Marker.ts new file mode 100644 index 0000000..78b438f --- /dev/null +++ b/src/world/graphics/Marker.ts @@ -0,0 +1,13 @@ +/// + +import Graphic = require('./Graphic'); + +class Marker { + constructor(){ + + } +} + +//extends Graphic + +export = Marker; \ No newline at end of file diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index 83fe0ed..d25d797 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -5,7 +5,7 @@ import MathUtils = require('../math/Math'); import MeshGraphic = require('../graphics/MeshGraphic'); import TileMaterial = require('../materials/TileMaterial'); import TileGeometry = require("../geometries/TileGeometry"); -import Vertice = require("../geometries/Vertice"); +import Vertice = require("../geometries/MeshVertice"); import Triangle = require("../geometries/Triangle"); class TileInfo { From f66a11204bbff929125e82ecb339fb9e6b735fa2 Mon Sep 17 00:00:00 2001 From: iSpring Date: Fri, 18 Nov 2016 00:14:02 +0800 Subject: [PATCH 030/109] add Poi --- index-bundle.html | 6 ++-- index-src.html | 6 ++-- main.js | 9 +++-- src/world/geometries/Marker.ts | 21 +++++++++++ src/world/graphics/Marker.ts | 13 ------- src/world/graphics/Poi.ts | 57 ++++++++++++++++++++++++++++++ src/world/layers/PoiLayer.ts | 24 +++++++++++++ src/world/materials/PoiMaterial.ts | 20 +++++++++++ 8 files changed, 135 insertions(+), 21 deletions(-) create mode 100644 src/world/geometries/Marker.ts delete mode 100644 src/world/graphics/Marker.ts create mode 100644 src/world/graphics/Poi.ts create mode 100644 src/world/layers/PoiLayer.ts create mode 100644 src/world/materials/PoiMaterial.ts diff --git a/index-bundle.html b/index-bundle.html index ce0ce02..3691819 100644 --- a/index-bundle.html +++ b/index-bundle.html @@ -12,9 +12,9 @@ - + + diff --git a/index-src.html b/index-src.html index 9e7f8cd..d6765c4 100644 --- a/index-src.html +++ b/index-src.html @@ -20,9 +20,9 @@ - + + diff --git a/main.js b/main.js index 156c5d5..a0aac74 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,9 @@ window.onload = function() { require(["world/Globe", "world/layers/BingTiledLayer", "world/layers/NokiaTiledLayer", "world/layers/OsmTiledLayer", - "world/layers/SosoTiledLayer", "world/layers/TiandituTiledLayer", "world/layers/GoogleTiledLayer"], - function(Globe, BingTiledLayer, NokiaTiledLayer, OsmTiledLayer, SosoTiledLayer, TiandituTiledLayer, GoogleTiledLayer) { + "world/layers/SosoTiledLayer", "world/layers/TiandituTiledLayer", "world/layers/GoogleTiledLayer", + "world/layers/PoiLayer"], + function(Globe, BingTiledLayer, NokiaTiledLayer, OsmTiledLayer, SosoTiledLayer, TiandituTiledLayer, GoogleTiledLayer, + PoiLayer) { function startWebGL() { var canvas = document.getElementById("canvasId"); @@ -9,6 +11,9 @@ var mapSelector = document.getElementById("mapSelector"); mapSelector.onchange = changeTiledLayer; changeTiledLayer(); + + var poiLayer = new PoiLayer(); + window.globe.scene.add(poiLayer); } function changeTiledLayer() { diff --git a/src/world/geometries/Marker.ts b/src/world/geometries/Marker.ts new file mode 100644 index 0000000..4f13c19 --- /dev/null +++ b/src/world/geometries/Marker.ts @@ -0,0 +1,21 @@ +/// + +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(){} +} + +export = Marker; \ No newline at end of file diff --git a/src/world/graphics/Marker.ts b/src/world/graphics/Marker.ts deleted file mode 100644 index 78b438f..0000000 --- a/src/world/graphics/Marker.ts +++ /dev/null @@ -1,13 +0,0 @@ -/// - -import Graphic = require('./Graphic'); - -class Marker { - constructor(){ - - } -} - -//extends Graphic - -export = Marker; \ 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..b4d1a65 --- /dev/null +++ b/src/world/graphics/Poi.ts @@ -0,0 +1,57 @@ +/// + +import Kernel = require("../Kernel"); +import Graphic = require('./Graphic'); +import Marker = require('../geometries/Marker'); +import PoiMaterial = require('../materials/PoiMaterial'); +import Program = require("../Program"); +import PerspectiveCamera = require("../PerspectiveCamera"); + +const vs = +` +attribute vec3 aPosition; +uniform mat4 uPMVMatrix; + +void main(void) { + gl_Position = uPMVMatrix * vec4(aPosition, 1.0); + gl_PointSize = 10.0; +} +`; + +const fs = +` +precision mediump float; + +void main() +{ + gl_FragColor = vec4(1.0, 1.0, 0.0, 0.5); +} +`; + +class Poi extends Graphic { + constructor(public geometry: Marker, public material: PoiMaterial){ + super(geometry, material); + } + + createProgram(){ + return new Program(this.getProgramType(), vs, fs); + } + + onDraw(camera: PerspectiveCamera){ + //aPosition + var locPosition = this.program.getAttribLocation('aPosition'); + this.program.enableVertexAttribArray('aPosition'); + this.geometry.vbo.bind(); + Kernel.gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); + + //uPMVMatrix + var pmvMatrix = camera.projViewMatrix;//.multiplyMatrix(this.geometry.matrix); + var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); + Kernel.gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); + + //绘图,1表示1个点 + Kernel.gl.drawArrays(Kernel.gl.POINTS, 0, 1); + } +} + +export = Poi; \ 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..ce38c13 --- /dev/null +++ b/src/world/layers/PoiLayer.ts @@ -0,0 +1,24 @@ +/// +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'); + +class PoiLayer extends GraphicGroup{ + constructor(){ + super(); + //Kernel.EARTH_RADIUS + 100 + //14198820 + var p = MathUtils.geographicToCartesianCoord(0, 0, Kernel.EARTH_RADIUS + 100); + var marker = new Marker(p.x, p.y, p.z); + //var marker = new Marker(0, 0, 14198820-100); + var material = new PoiMaterial(); + var poi = new Poi(marker, material); + this.add(poi); + } +} + +export = PoiLayer; \ 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..4e430cf --- /dev/null +++ b/src/world/materials/PoiMaterial.ts @@ -0,0 +1,20 @@ +/// + +import Material = require("./Material"); + +class PoiMaterial implements Material{ + + isReady(){ + return true; + } + + getType(){ + return "PoiMaterial"; + } + + destroy(){ + + } +} + +export = PoiMaterial; \ No newline at end of file From ce7e92e99c74df86519cff2a4317ec44505f38da Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 18 Nov 2016 12:06:36 +0800 Subject: [PATCH 031/109] update --- src/world/Globe.ts | 8 ++++---- src/world/PerspectiveCamera.ts | 11 +++++++++++ src/world/Renderer.ts | 15 +++++++-------- src/world/layers/SubTiledLayer.ts | 28 ++++++++++++++-------------- src/world/layers/TiledLayer.ts | 9 +++++++++ 5 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 2baddda..25fb695 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -30,8 +30,8 @@ class Globe { 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.renderer.setScene(this.scene); + this.renderer.setCamera(this.camera); this.setLevel(0); this.renderer.setIfAutoRefresh(true); EventUtils.initLayout(); @@ -103,7 +103,7 @@ class Globe { level = level > this.MAX_LEVEL ? this.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 if(level !== this.CURRENT_LEVEL){ this.camera.animateToLevel(level); - } + } } } @@ -170,7 +170,7 @@ class Globe { levelsTileGrids.splice(0, 1); } } - + } export = Globe; \ No newline at end of file diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index d55d6b7..5609021 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -107,6 +107,17 @@ class PerspectiveCamera extends Object3D { return this.matrix.getInverseMatrix(); } + updateViewMatrix(): Matrix{ + this.viewMatrix = this.getViewMatrix(); + return this.viewMatrix; + } + + updateProjViewMatrix(): Matrix{ + this.updateViewMatrix(); + this.projViewMatrix = this.projMatrix.multiplyMatrix(this.viewMatrix); + return this.projViewMatrix; + } + look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { var cameraPntCopy = cameraPnt.clone(); var targetPntCopy = targetPnt.clone(); diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index bd18198..3446071 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -13,7 +13,7 @@ class Renderer { constructor(canvas: HTMLCanvasElement) { //之所以在此处设置Kernel.renderer是因为要在tick函数中使用 Kernel.renderer = this; - + EventUtils.bindEvents(canvas); var gl: WebGLRenderingContextExtension; @@ -43,12 +43,12 @@ class Renderer { } gl.clearColor(255, 255, 255, 1.0); - //gl.enable(gl.DEPTH_TEST); - gl.disable(gl.DEPTH_TEST); //此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 + gl.enable(gl.DEPTH_TEST); + //gl.disable(gl.DEPTH_TEST); //此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 gl.depthFunc(gl.LEQUAL); 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 @@ -59,16 +59,15 @@ class Renderer { Kernel.gl.clear(Kernel.gl.COLOR_BUFFER_BIT | Kernel.gl.DEPTH_BUFFER_BIT); camera.viewMatrix = null; //update viewMatrix and projViewMatrix of camera - camera.viewMatrix = camera.getViewMatrix(); - camera.projViewMatrix = camera.projMatrix.multiplyMatrix(camera.viewMatrix); + camera.updateProjViewMatrix(); scene.draw(camera); } - bindScene(scene: Scene) { + setScene(scene: Scene) { this.scene = scene; } - bindCamera(camera: PerspectiveCamera) { + setCamera(camera: PerspectiveCamera) { this.camera = camera; } diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index 3c4b8e8..d5a9d6b 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -15,20 +15,20 @@ class SubTiledLayer extends GraphicGroup { this.level = args.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); - }*/ - Kernel.gl.disable(Kernel.gl.DEPTH_TEST); - super.draw(camera); - } + //重写GraphicGroup的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); + // }*/ + // Kernel.gl.disable(Kernel.gl.DEPTH_TEST);//此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 + // super.draw(camera); + // } - //重写Object3DComponents的add方法 + //重写GraphicGroup的add方法 add(tile: Tile) { if (tile.tileInfo.level === this.level) { super.add(tile); @@ -36,7 +36,7 @@ class SubTiledLayer extends GraphicGroup { } } - //重写Object3DComponents的destroy方法 + //重写GraphicGroup的destroy方法 destroy() { super.destroy(); this.tiledLayer = null; diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index e4ef093..d68af50 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -2,8 +2,17 @@ import Kernel = require('../Kernel'); import GraphicGroup = require('../GraphicGroup'); import SubTiledLayer = require('./SubTiledLayer'); +import PerspectiveCamera = require('../PerspectiveCamera'); abstract class TiledLayer extends GraphicGroup { + + //重写 + draw(camera: PerspectiveCamera){ + Kernel.gl.disable(Kernel.gl.DEPTH_TEST);//此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 + super.draw(camera); + Kernel.gl.enable(Kernel.gl.DEPTH_TEST);//恢复深度测试 + } + //重写 add(subTiledLayer: SubTiledLayer) { super.add(subTiledLayer); From c7a30fc7c8b9a7056c5309eecec6786b826d90bc Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 18 Nov 2016 14:48:42 +0800 Subject: [PATCH 032/109] update --- main.js | 1 + src/world/Globe.ts | 5 +---- src/world/GraphicGroup.ts | 8 ++++++-- src/world/Renderer.ts | 7 ++++++- src/world/graphics/Poi.ts | 4 +++- src/world/layers/PoiLayer.ts | 10 +++++++--- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/main.js b/main.js index a0aac74..62e9a21 100644 --- a/main.js +++ b/main.js @@ -8,6 +8,7 @@ function startWebGL() { var canvas = document.getElementById("canvasId"); window.globe = new Globe(canvas); + var mapSelector = document.getElementById("mapSelector"); mapSelector.onchange = changeTiledLayer; changeTiledLayer(); diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 25fb695..edd26f9 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -23,9 +23,6 @@ class Globe { constructor(canvas: HTMLCanvasElement, args: any) { args = args || {}; 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); this.scene = new Scene(); var radio = canvas.width / canvas.height; @@ -49,7 +46,7 @@ class Globe { this.scene.tiledLayer = null; } this.tiledLayer = tiledLayer; - this.scene.add(this.tiledLayer); + this.scene.add(this.tiledLayer, true); //添加第0级的子图层 var subLayer0 = new SubTiledLayer({ level: 0 diff --git a/src/world/GraphicGroup.ts b/src/world/GraphicGroup.ts index c2cc456..f7b6f64 100644 --- a/src/world/GraphicGroup.ts +++ b/src/world/GraphicGroup.ts @@ -16,8 +16,12 @@ class GraphicGroup{ this.children = []; } - add(g: Drawable){ - this.children.push(g); + add(g: Drawable, first: boolean = false){ + if(first){ + this.children.unshift(g); + }else{ + this.children.push(g); + } g.parent = this; } diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 3446071..9f73279 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -42,10 +42,12 @@ class Renderer { return; } + 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.depthFunc(gl.LEQUAL); + gl.depthMask(true); gl.enable(gl.CULL_FACE); //一定要启用裁剪,否则显示不出立体感 gl.frontFace(gl.CCW);//指定逆时针方向为正面 @@ -57,6 +59,9 @@ class Renderer { 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); + Kernel.gl.enable(Kernel.gl.DEPTH_TEST); + Kernel.gl.depthFunc(Kernel.gl.LEQUAL); + Kernel.gl.depthMask(true); camera.viewMatrix = null; //update viewMatrix and projViewMatrix of camera camera.updateProjViewMatrix(); diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index b4d1a65..ddcca34 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -11,7 +11,7 @@ const vs = ` attribute vec3 aPosition; uniform mat4 uPMVMatrix; - + void main(void) { gl_Position = uPMVMatrix * vec4(aPosition, 1.0); gl_PointSize = 10.0; @@ -28,6 +28,8 @@ void main() } `; +//gl_FragColor = vec4(1.0, 1.0, 0.0, 0.5); + class Poi extends Graphic { constructor(public geometry: Marker, public material: PoiMaterial){ super(geometry, material); diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts index ce38c13..ca22020 100644 --- a/src/world/layers/PoiLayer.ts +++ b/src/world/layers/PoiLayer.ts @@ -6,19 +6,23 @@ import GraphicGroup = require('../GraphicGroup'); import Poi = require('../graphics/Poi'); import Marker = require('../geometries/Marker'); import PoiMaterial = require('../materials/PoiMaterial'); +import PerspectiveCamera = require('../PerspectiveCamera'); class PoiLayer extends GraphicGroup{ constructor(){ super(); - //Kernel.EARTH_RADIUS + 100 - //14198820 - var p = MathUtils.geographicToCartesianCoord(0, 0, Kernel.EARTH_RADIUS + 100); + var p = MathUtils.geographicToCartesianCoord(0, 0, Kernel.EARTH_RADIUS + 1000000); var marker = new Marker(p.x, p.y, p.z); //var marker = new Marker(0, 0, 14198820-100); var material = new PoiMaterial(); var poi = new Poi(marker, material); this.add(poi); } + + draw(camera: PerspectiveCamera){ + Kernel.gl.enable(Kernel.gl.DEPTH_TEST); + super.draw(camera); + } } export = PoiLayer; \ No newline at end of file From c5f528e9b67b5f5e55bd0d41103c0376c2215e00 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 18 Nov 2016 16:32:17 +0800 Subject: [PATCH 033/109] update PerspectiveCamera --- src/world/PerspectiveCamera.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index 5609021..4517526 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -30,28 +30,29 @@ class PerspectiveCamera extends Object3D { } setPerspectiveMatrix(fov: number = 90, aspect: number = 1, near: number = 1, far: number = 1): void { + //https://github.com/toji/gl-matrix/blob/master/src/gl-matrix/mat4.js#L1788 this.fov = fov; this.aspect = aspect; this.near = near; this.far = far; - var mat = [1, 0, 0, 0, + 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; + var f = 1 / Math.tan(halfFov); + var nf = 1 / (this.near - this.far); - mat[0] = a / this.aspect; - mat[5] = a; - mat[10] = -(this.far + this.near) / b; + mat[0] = f / this.aspect; + mat[5] = f; + mat[10] = (this.far + this.near) * nf; mat[11] = -1; - mat[14] = -2 * this.near * this.far / b; + mat[14] = 2 * this.near * this.far * nf; mat[15] = 0; - //by comparision with matrixProjection.exe and glMatrix, - //the 11th element is always -1 + //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], From 6142eb50d12b38ee7e34c6090890c3992b6112f0 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 18 Nov 2016 17:31:25 +0800 Subject: [PATCH 034/109] update --- src/world/math/Math.ts | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index b9603e7..be778ef 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -331,7 +331,7 @@ const MathUtils = { * @return {*} */ getLengthFromCamera2EarthSurface(level: number): number{ - return 7820683/Math.pow(2,level); + return 7820683 / Math.pow(2, level); }, /**将经纬度转换为笛卡尔空间直角坐标系中的x、y、z @@ -372,8 +372,7 @@ const MathUtils = { var sin2 = y/Kernel.EARTH_RADIUS; if(sin2 > 1){ sin2 = 2; - } - else if(sin2 < -1){ + }else if(sin2 < -1){ sin2 = -1; } var radianLat = Math.asin(sin2); @@ -381,31 +380,26 @@ const MathUtils = { var sin1 = x / (Kernel.EARTH_RADIUS * cos2); if(sin1 > 1){ sin1 = 1; - } - else if(sin1 < -1){ + }else if(sin1 < -1){ sin1 = -1; } var cos1 = z / (Kernel.EARTH_RADIUS * cos2); if(cos1 > 1){ cos1 = 1; - } - else if(cos1 < -1){ + }else if(cos1 < -1){ cos1 = -1; } var radianLog = Math.asin(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; } } From 56ad2007289881c963fc4e6dbbea1d66e30c2699 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sun, 20 Nov 2016 23:56:45 +0800 Subject: [PATCH 035/109] update --- src/world/Renderer.ts | 1 + src/world/graphics/Poi.ts | 4 +--- src/world/layers/PoiLayer.ts | 2 +- src/world/layers/SubTiledLayer.ts | 13 ------------- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 9f73279..9d9bd99 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -27,6 +27,7 @@ class Renderer { }) as WebGLRenderingContextExtension; if (gl) { Kernel.gl = gl; + (window).gl = gl; Kernel.canvas = canvas; break; } diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index ddcca34..955e95a 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -24,12 +24,10 @@ precision mediump float; void main() { - gl_FragColor = vec4(1.0, 1.0, 0.0, 0.5); + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `; -//gl_FragColor = vec4(1.0, 1.0, 0.0, 0.5); - class Poi extends Graphic { constructor(public geometry: Marker, public material: PoiMaterial){ super(geometry, material); diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts index ca22020..11f2aab 100644 --- a/src/world/layers/PoiLayer.ts +++ b/src/world/layers/PoiLayer.ts @@ -11,7 +11,7 @@ import PerspectiveCamera = require('../PerspectiveCamera'); class PoiLayer extends GraphicGroup{ constructor(){ super(); - var p = MathUtils.geographicToCartesianCoord(0, 0, Kernel.EARTH_RADIUS + 1000000); + var p = MathUtils.geographicToCartesianCoord(180, 0, Kernel.EARTH_RADIUS + 1000000); var marker = new Marker(p.x, p.y, p.z); //var marker = new Marker(0, 0, 14198820-100); var material = new PoiMaterial(); diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index d5a9d6b..91c4ba9 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -15,19 +15,6 @@ class SubTiledLayer extends GraphicGroup { this.level = args.level; } - //重写GraphicGroup的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); - // }*/ - // Kernel.gl.disable(Kernel.gl.DEPTH_TEST);//此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 - // super.draw(camera); - // } - //重写GraphicGroup的add方法 add(tile: Tile) { if (tile.tileInfo.level === this.level) { From ee9cd69fe0d6e67c7f60ca25b7f2dab7468ad86d Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Mon, 21 Nov 2016 14:27:14 +0800 Subject: [PATCH 036/109] decrease EARTH_RADIUS and MAX_PROJECTED_COORD to fix the depth test issue,#10 --- src/world/Globe.ts | 2 +- src/world/Kernel.ts | 4 ++-- src/world/graphics/Tile.ts | 6 +++--- src/world/layers/PoiLayer.ts | 19 ++++++++++++++----- src/world/layers/TiledLayer.ts | 6 ++++-- src/world/math/Math.ts | 3 ++- 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index edd26f9..abb363d 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -26,7 +26,7 @@ class Globe { this.renderer = Kernel.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.camera = new PerspectiveCamera(30, radio, 1, Kernel.EARTH_RADIUS * 2); this.renderer.setScene(this.scene); this.renderer.setCamera(this.camera); this.setLevel(0); diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index e408947..12d4aea 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -10,8 +10,8 @@ const Kernel = { globe: null, idCounter: 0, //Object3D对象的唯一标识 BASE_LEVEL: 6, //渲染的基准层级 - EARTH_RADIUS: 6378137, - MAX_PROJECTED_COORD: 20037508.3427892, + EARTH_RADIUS: 637.8137,//6378137 + MAX_PROJECTED_COORD: 2003.75083427892,//20037508.3427892 proxy: "" }; diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index d25d797..cde99c5 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -46,7 +46,7 @@ class TileInfo { 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]; @@ -81,7 +81,7 @@ class TileInfo { var deltaTextureCoord = 1.0 / this.segment; var changeElevation = this.type === Enum.TERRAIN_TILE && this.elevationInfo; //level不同设置的半径也不同 - var levelDeltaR = 0; //this.level * 100; + var levelDeltaR = 0;//this.level * 2; //对WebMercator投影进行等间距划分格网 var mercatorXs: number[] = []; //存储从最小的x到最大x的分割值 var mercatorYs: number[] = []; //存储从最大的y到最小的y的分割值 @@ -117,7 +117,7 @@ class TileInfo { verticeArray.push(v); index++; } - } + } //从左上到右下填充indices //添加的点的顺序:左上->左下->右下->右上 diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts index 11f2aab..d655362 100644 --- a/src/world/layers/PoiLayer.ts +++ b/src/world/layers/PoiLayer.ts @@ -7,11 +7,20 @@ import Poi = require('../graphics/Poi'); import Marker = require('../geometries/Marker'); import PoiMaterial = require('../materials/PoiMaterial'); import PerspectiveCamera = require('../PerspectiveCamera'); +import Box = require('../geometries/Box'); +import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); +import MeshGraphic = require('../graphics/MeshGraphic'); class PoiLayer extends GraphicGroup{ constructor(){ super(); - var p = MathUtils.geographicToCartesianCoord(180, 0, Kernel.EARTH_RADIUS + 1000000); + // var d = Kernel.EARTH_RADIUS * 2; + // var box = new Box(d, d, d); + // var material2 = new MeshTextureMaterial("http://gallery.esri.com/WebGlobe/images/test.png"); + // var boxGraphic = new MeshGraphic(box, material2); + // this.add(boxGraphic); + + var p = MathUtils.geographicToCartesianCoord(0, 0, Kernel.EARTH_RADIUS * 1.2); var marker = new Marker(p.x, p.y, p.z); //var marker = new Marker(0, 0, 14198820-100); var material = new PoiMaterial(); @@ -19,10 +28,10 @@ class PoiLayer extends GraphicGroup{ this.add(poi); } - draw(camera: PerspectiveCamera){ - Kernel.gl.enable(Kernel.gl.DEPTH_TEST); - super.draw(camera); - } + // draw(camera: PerspectiveCamera){ + // Kernel.gl.enable(Kernel.gl.DEPTH_TEST); + // super.draw(camera); + // } } export = PoiLayer; \ No newline at end of file diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index d68af50..2892845 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -8,9 +8,11 @@ abstract class TiledLayer extends GraphicGroup { //重写 draw(camera: PerspectiveCamera){ - Kernel.gl.disable(Kernel.gl.DEPTH_TEST);//此处禁用深度测试是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 + //此处将深度测试设置为ALWAYS是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 + Kernel.gl.depthFunc(Kernel.gl.ALWAYS); super.draw(camera); - Kernel.gl.enable(Kernel.gl.DEPTH_TEST);//恢复深度测试 + //将深度测试恢复成LEQUAL + Kernel.gl.depthFunc(Kernel.gl.LEQUAL); } //重写 diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index be778ef..76f419d 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -331,7 +331,8 @@ const MathUtils = { * @return {*} */ getLengthFromCamera2EarthSurface(level: number): number{ - return 7820683 / Math.pow(2, level); + //7820683 + return 1.2261704318988444 * Kernel.EARTH_RADIUS / Math.pow(2, level); }, /**将经纬度转换为笛卡尔空间直角坐标系中的x、y、z From baa08021846432255153e097a81e24a5e204691f Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Mon, 21 Nov 2016 15:57:33 +0800 Subject: [PATCH 037/109] increase EARTH_RADIUS and MAX_PROJECTED_COORD to let level14 visible,#10 --- src/world/Kernel.ts | 4 ++-- src/world/math/Math.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index 12d4aea..6a0484c 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -10,8 +10,8 @@ const Kernel = { globe: null, idCounter: 0, //Object3D对象的唯一标识 BASE_LEVEL: 6, //渲染的基准层级 - EARTH_RADIUS: 637.8137,//6378137 - MAX_PROJECTED_COORD: 2003.75083427892,//20037508.3427892 + EARTH_RADIUS: 637.8137*26,//6378137 + MAX_PROJECTED_COORD: 2003.75083427892*26,//20037508.3427892 proxy: "" }; diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index 76f419d..56c5e6f 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -332,7 +332,8 @@ const MathUtils = { */ getLengthFromCamera2EarthSurface(level: number): number{ //7820683 - return 1.2261704318988444 * Kernel.EARTH_RADIUS / Math.pow(2, level); + //1.2261704318988444 * Kernel.EARTH_RADIUS / Math.pow(2, level); + return Kernel.EARTH_RADIUS / Math.pow(2, level); }, /**将经纬度转换为笛卡尔空间直角坐标系中的x、y、z From 07a5d873222c7c48013b29f0a229c7cf4dd9737b Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Mon, 21 Nov 2016 17:18:00 +0800 Subject: [PATCH 038/109] fix the depth test issue,#10 --- package.json | 2 +- src/world/Kernel.ts | 7 +++++-- src/world/math/Math.ts | 2 +- versions.txt | 4 +++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 11b1239..01c3981 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webglobe", - "version": "0.2.3", + "version": "0.2.4", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index 6a0484c..e3ad69d 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -3,6 +3,9 @@ import {WebGLRenderingContextExtension} from './Definitions'; import Globe = require("./Globe"); import Renderer = require("./Renderer"); +const radius = 14000;//6378137 +const maxProjectedCoord = Math.PI * radius; + const Kernel = { gl: null, canvas: null, @@ -10,8 +13,8 @@ const Kernel = { globe: null, idCounter: 0, //Object3D对象的唯一标识 BASE_LEVEL: 6, //渲染的基准层级 - EARTH_RADIUS: 637.8137*26,//6378137 - MAX_PROJECTED_COORD: 2003.75083427892*26,//20037508.3427892 + EARTH_RADIUS: radius, + MAX_PROJECTED_COORD: maxProjectedCoord, proxy: "" }; diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index 56c5e6f..c6b909c 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -333,7 +333,7 @@ const MathUtils = { getLengthFromCamera2EarthSurface(level: number): number{ //7820683 //1.2261704318988444 * Kernel.EARTH_RADIUS / Math.pow(2, level); - return Kernel.EARTH_RADIUS / Math.pow(2, level); + return 1.23 * Kernel.EARTH_RADIUS / Math.pow(2, level); }, /**将经纬度转换为笛卡尔空间直角坐标系中的x、y、z diff --git a/versions.txt b/versions.txt index ddff7c5..80869e4 100644 --- a/versions.txt +++ b/versions.txt @@ -92,4 +92,6 @@ 0.2.2 删除高程相关代码 -0.2.3 将world/geometries/Geomtry重命名为world/geometries/Mesh,并在package.json中添加了多个npm scripts,对应gulp中定义的tasks \ No newline at end of file +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这两个类。 \ No newline at end of file From 95c7af2c0d67f65fceec80b6edf4b1760494d13a Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Mon, 21 Nov 2016 18:47:47 +0800 Subject: [PATCH 039/109] basically make Poi display marker with texture,#12 --- src/world/Renderer.ts | 2 +- src/world/graphics/MeshGraphic.ts | 24 +++++++------ src/world/graphics/Poi.ts | 32 +++++++++++++---- src/world/images/poi.png | Bin 0 -> 336 bytes src/world/layers/PoiLayer.ts | 16 ++------- src/world/materials/MeshTextureMaterial.ts | 38 ++++++++++++--------- src/world/materials/PoiMaterial.ts | 16 ++++----- src/world/math/Math.ts | 2 -- 8 files changed, 71 insertions(+), 59 deletions(-) create mode 100644 src/world/images/poi.png diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 9d9bd99..9b57df6 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -48,7 +48,7 @@ class Renderer { gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); - gl.depthMask(true); + gl.depthMask(true);//允许写入深度 gl.enable(gl.CULL_FACE); //一定要启用裁剪,否则显示不出立体感 gl.frontFace(gl.CCW);//指定逆时针方向为正面 diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index d9ced79..b94b888 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -54,31 +54,35 @@ class MeshGraphic extends Graphic { } _drawTextureMaterial(program: any) { + var gl = Kernel.gl; + //set aUV var locUV = program.getAttribLocation('aUV'); program.enableVertexAttribArray('aUV'); this.geometry.uvbo.bind(); - Kernel.gl.vertexAttribPointer(locUV, 2, Kernel.gl.FLOAT, false, 0, 0); + gl.vertexAttribPointer(locUV, 2, gl.FLOAT, false, 0, 0); //set uSampler var locSampler = program.getUniformLocation('uSampler'); - Kernel.gl.activeTexture(Kernel.gl.TEXTURE0); + gl.activeTexture(gl.TEXTURE0); //world.Cache.activeTexture(gl.TEXTURE0); - Kernel.gl.bindTexture(Kernel.gl.TEXTURE_2D, this.material.texture); - Kernel.gl.uniform1i(locSampler, 0); + gl.bindTexture(gl.TEXTURE_2D, this.material.texture); + gl.uniform1i(locSampler, 0); } onDraw(camera: PerspectiveCamera) { + var gl = Kernel.gl; + //aPosition var locPosition = this.program.getAttribLocation('aPosition'); this.program.enableVertexAttribArray('aPosition'); this.geometry.vbo.bind(); - Kernel.gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); + gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 0, 0); //uPMVMatrix var pmvMatrix = camera.projViewMatrix.multiplyMatrix(this.geometry.matrix); var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); - Kernel.gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); + gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); this._drawTextureMaterial(this.program); @@ -87,12 +91,12 @@ class MeshGraphic extends Graphic { //绘图 var count = this.geometry.triangles.length * 3; - Kernel.gl.drawElements(Kernel.gl.TRIANGLES, count, Kernel.gl.UNSIGNED_SHORT, 0); + gl.drawElements(gl.TRIANGLES, count, 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); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); } } diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index 955e95a..e694076 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -11,20 +11,22 @@ const vs = ` attribute vec3 aPosition; uniform mat4 uPMVMatrix; +uniform float uSize; void main(void) { gl_Position = uPMVMatrix * vec4(aPosition, 1.0); - gl_PointSize = 10.0; + gl_PointSize = uSize; } `; const fs = ` precision mediump float; +uniform sampler2D uSampler; void main() { - gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + gl_FragColor = texture2D(uSampler, gl_PointCoord); } `; @@ -38,19 +40,37 @@ class Poi extends Graphic { } onDraw(camera: PerspectiveCamera){ + var gl = Kernel.gl; + //aPosition var locPosition = this.program.getAttribLocation('aPosition'); this.program.enableVertexAttribArray('aPosition'); this.geometry.vbo.bind(); - Kernel.gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); + gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 0, 0); //uPMVMatrix - var pmvMatrix = camera.projViewMatrix;//.multiplyMatrix(this.geometry.matrix); + var pmvMatrix = camera.projViewMatrix; var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); - Kernel.gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); + 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个点 - Kernel.gl.drawArrays(Kernel.gl.POINTS, 0, 1); + gl.drawArrays(gl.POINTS, 0, 1); + + //释放当前绑定对象 + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); } } diff --git a/src/world/images/poi.png b/src/world/images/poi.png new file mode 100644 index 0000000000000000000000000000000000000000..dc00c817391fb51fd4a0769d87c63cca8ffa5b61 GIT binary patch literal 336 zcmV-W0k8gvP)owzhkfva$9o_19cD&|e=3xFLm>64pcih+pL{~OJw)?ite|J+%Hfgh3ktVG zbOztJ>D$mVW4F}d(Gwj9S0mi(bDVYO$hKw*dS>i6<9I<#dq%Nm;j}YDswt7awxMUn zjx&xI#0xqSwJ+>9Bx08q_Nq4Y%-C_p@q$`!gvjwbcF!l+Dar-bFH&XfIJ49}T-<#i zoHwzpSqN)Wh4PDxohvUXUmsvI^NFpase+!FtAF9J@`zwYD(J;3w2?MJ#Hv(knIM02 i!!UfplP6D})>uECV*Fl>dLzmJ0000 -import Material = require("./Material"); +import MeshTextureMaterial = require("./MeshTextureMaterial"); -class PoiMaterial implements Material{ - - isReady(){ - return true; +type ImageType = string | HTMLImageElement; + +class PoiMaterial extends MeshTextureMaterial{ + + constructor(imageOrUrl?: ImageType, public size:number = 16){ + super(imageOrUrl, true); } getType(){ return "PoiMaterial"; } - - destroy(){ - - } } export = PoiMaterial; \ No newline at end of file diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index c6b909c..2f21e1d 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -331,8 +331,6 @@ const MathUtils = { * @return {*} */ getLengthFromCamera2EarthSurface(level: number): number{ - //7820683 - //1.2261704318988444 * Kernel.EARTH_RADIUS / Math.pow(2, level); return 1.23 * Kernel.EARTH_RADIUS / Math.pow(2, level); }, From 59b35c7a4edf895b5fe15f74f3bdca802df1a9be Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Mon, 21 Nov 2016 18:48:02 +0800 Subject: [PATCH 040/109] add comment --- src/world/graphics/MeshGraphic.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index b94b888..88bde17 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -21,6 +21,7 @@ void main() } `; +//http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 const fs = ` precision mediump float; From 205268c392f0f6a86502951089655a1996732fc1 Mon Sep 17 00:00:00 2001 From: iSpring Date: Mon, 21 Nov 2016 23:23:09 +0800 Subject: [PATCH 041/109] enable blend for poi,#12 --- src/world/graphics/MeshGraphic.ts | 1 - src/world/graphics/Poi.ts | 7 ++++++- src/world/images/pin.png | Bin 0 -> 2428 bytes src/world/layers/PoiLayer.ts | 4 ++-- src/world/materials/PoiMaterial.ts | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 src/world/images/pin.png diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index 88bde17..b94b888 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -21,7 +21,6 @@ void main() } `; -//http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 const fs = ` precision mediump float; diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index e694076..f32066b 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -19,6 +19,7 @@ void main(void) { } `; +//http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 const fs = ` precision mediump float; @@ -26,7 +27,7 @@ uniform sampler2D uSampler; void main() { - gl_FragColor = texture2D(uSampler, gl_PointCoord); + gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y)); } `; @@ -42,6 +43,9 @@ class Poi extends Graphic { onDraw(camera: PerspectiveCamera){ var gl = Kernel.gl; + 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'); @@ -68,6 +72,7 @@ class Poi extends Graphic { gl.drawArrays(gl.POINTS, 0, 1); //释放当前绑定对象 + gl.disable(gl.BLEND); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); gl.bindTexture(gl.TEXTURE_2D, null); diff --git a/src/world/images/pin.png b/src/world/images/pin.png new file mode 100644 index 0000000000000000000000000000000000000000..727591d3e4d91eae6349049cb393cd2d8ac83827 GIT binary patch literal 2428 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 Date: Mon, 21 Nov 2016 23:59:02 +0800 Subject: [PATCH 042/109] update,#12 --- src/world/graphics/Poi.ts | 2 ++ src/world/layers/PoiLayer.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index f32066b..1dde278 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -43,6 +43,7 @@ class Poi extends Graphic { onDraw(camera: PerspectiveCamera){ var gl = Kernel.gl; + gl.disable(gl.DEPTH_TEST); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); @@ -72,6 +73,7 @@ class Poi extends Graphic { 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); diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts index d42e4ae..821052a 100644 --- a/src/world/layers/PoiLayer.ts +++ b/src/world/layers/PoiLayer.ts @@ -13,7 +13,7 @@ class PoiLayer extends GraphicGroup{ constructor(){ super(); - var p = MathUtils.geographicToCartesianCoord(0, 0, Kernel.EARTH_RADIUS); + var p = MathUtils.geographicToCartesianCoord(116.408540, 39.902350, Kernel.EARTH_RADIUS); var marker = new Marker(p.x, p.y, p.z); var url = "/WebGlobe/src/world/images/poi.png"; var material = new PoiMaterial(url, 24); From 46b03554fbd06f7e1fa54420007c091b6a8433f7 Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 22 Nov 2016 00:13:23 +0800 Subject: [PATCH 043/109] upadte poi,#12 --- src/world/graphics/Poi.ts | 4 ++-- src/world/layers/PoiLayer.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index 1dde278..8b741f9 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -43,7 +43,7 @@ class Poi extends Graphic { onDraw(camera: PerspectiveCamera){ var gl = Kernel.gl; - gl.disable(gl.DEPTH_TEST); + //gl.disable(gl.DEPTH_TEST); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); @@ -73,7 +73,7 @@ class Poi extends Graphic { gl.drawArrays(gl.POINTS, 0, 1); //释放当前绑定对象 - gl.enable(gl.DEPTH_TEST); + //gl.enable(gl.DEPTH_TEST); gl.disable(gl.BLEND); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts index 821052a..214b7d1 100644 --- a/src/world/layers/PoiLayer.ts +++ b/src/world/layers/PoiLayer.ts @@ -13,7 +13,7 @@ class PoiLayer extends GraphicGroup{ constructor(){ super(); - var p = MathUtils.geographicToCartesianCoord(116.408540, 39.902350, Kernel.EARTH_RADIUS); + var p = MathUtils.geographicToCartesianCoord(116.408540, 39.902350, Kernel.EARTH_RADIUS + 0.001); var marker = new Marker(p.x, p.y, p.z); var url = "/WebGlobe/src/world/images/poi.png"; var material = new PoiMaterial(url, 24); From f71c63f670e3268e392a865754fa11bcc771aa22 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 25 Nov 2016 15:52:59 +0800 Subject: [PATCH 044/109] calculate camera.far after call camera._setLevel() method --- src/world/PerspectiveCamera.ts | 45 +++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index 4517526..f5cdcb6 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -22,14 +22,14 @@ class PerspectiveCamera extends Object3D { }; private animating: boolean = false; - constructor(public fov = 90, public aspect = 1, public near = 1, public far = 1) { + constructor(public fov = 45, public aspect = 1, public near = 1, public far = 100) { super(); 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 { + setPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { //https://github.com/toji/gl-matrix/blob/master/src/gl-matrix/mat4.js#L1788 this.fov = fov; this.aspect = aspect; @@ -68,6 +68,12 @@ class PerspectiveCamera extends Object3D { return direction; } + getDistance2EarthSurface(): number { + var position = this.getPosition(); + var length2EarthSurface = Vector.fromVertice(position).getLength() - Kernel.EARTH_RADIUS; + return length2EarthSurface; + } + //获取投影矩阵与视点矩阵的乘积 getProjViewMatrix(): Matrix { var viewMatrix = this.getViewMatrix(); @@ -294,19 +300,19 @@ class PerspectiveCamera extends Object3D { } animateToLevel(level: number): void { - var newMat = this._animateToLevel(level); - this._animateToMatrix(newMat, () => { + var newCamera = this._animateToLevel(level); + this._animateToCamera(newCamera, () => { Kernel.globe.CURRENT_LEVEL = level; }); } - private _animateToMatrix(newMat: Matrix, cb: ()=>void){ + private _animateToCamera(newCamera: PerspectiveCamera, cb: ()=>void){ if(this.isAnimating()){ return; } this.animating = true; var oldPosition = this.getPosition(); - var newPosition = newMat.getPosition(); + var newPosition = newCamera.matrix.getPosition(); var span = this.animationDuration; var singleSpan = 1000 / 60; var count = Math.floor(span / singleSpan); @@ -320,7 +326,7 @@ class PerspectiveCamera extends Object3D { } var a = timestap - start; if(a >= span){ - this.matrix = newMat; + (Object).assign(this, newCamera.toJson()); this.animating = false; cb(); }else{ @@ -332,24 +338,34 @@ class PerspectiveCamera extends Object3D { requestAnimationFrame(callback); } - private _animateToLevel(level: number): Matrix{ + private _animateToLevel(level: number): PerspectiveCamera{ 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; + return camera; } private _clone(): PerspectiveCamera{ var camera: PerspectiveCamera = new PerspectiveCamera(); - camera.pitch = this.pitch; - camera.matrix = this.matrix.clone(); - camera.projMatrix = this.projMatrix.clone(); + (Object).assign(camera, this.toJson()); return camera; } + toJson(): any { + return { + pitch: this.pitch, + near: this.near, + far: this.far, + fov: this.fov, + aspect: this.aspect, + matrix: this.matrix.clone(), + projMatrix: this.projMatrix.clone() + }; + } + setLevel(level: number): void{ this._setLevel(level); //don't update CURRENT_LEVEL in _setLevel method because it will affect animateToLevel method @@ -379,6 +395,11 @@ class PerspectiveCamera extends Object3D { var pNew = Vector.verticePlusVector(pOld, dir); this.setPosition(pNew.x, pNew.y, pNew.z); } + //重新计算far,保持far在满足正常需求情况下的最小值 + //far值:视点与地球切面的距离 + var length2EarthOrigin = Vector.fromVertice(this.getPosition()).getLength(); + var far = Math.sqrt(length2EarthOrigin * length2EarthOrigin - Kernel.EARTH_RADIUS * Kernel.EARTH_RADIUS); + this.setFar(far * 1.05); } //判断世界坐标系中的点是否在Canvas中可见 From 8ea998dbd8d72004f70eacd218b664703565a50f Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 25 Nov 2016 15:53:53 +0800 Subject: [PATCH 045/109] add vscode settings.json --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json 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 From b6e42a1c19a856694bf6bbecc1bb664e100f74cf Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 25 Nov 2016 18:07:42 +0800 Subject: [PATCH 046/109] update --- src/world/PerspectiveCamera.ts | 59 ++++++++++++++++++++++------------ src/world/geometries/Marker.ts | 5 ++- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index f5cdcb6..70a7694 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -14,7 +14,7 @@ class PerspectiveCamera extends Object3D { private animationDuration = 600;//层级变化的动画周期是600毫秒 pitch: number; viewMatrix: Matrix; - projMatrix: Matrix; + projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far projViewMatrix: Matrix; Enum: any = { EARTH_FULL_OVERSPREAD_SCREEN: "EARTH_FULL_OVERSPREAD_SCREEN", //Canvas内全部被地球充满 @@ -26,10 +26,15 @@ class PerspectiveCamera extends Object3D { super(); this.pitch = 90; this.projMatrix = new Matrix(); - this.setPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); + this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); } - setPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { + _setPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { + this._rawSetPerspectiveMatrix(fov, aspect, near, far); + this._updateFar(); + } + + _rawSetPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { //https://github.com/toji/gl-matrix/blob/master/src/gl-matrix/mat4.js#L1788 this.fov = fov; this.aspect = aspect; @@ -85,28 +90,37 @@ class PerspectiveCamera extends Object3D { if (!(fov > 0)) { throw "invalid fov:" + fov; } - this.setPerspectiveMatrix(fov, this.aspect, this.near, this.far); + 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); + 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); + 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); + // setFar(far: number): void { + // if (!(far > 0)) { + // throw "invalid far:" + far; + // } + // this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); + // } + + _updateFar():void{ + //重新计算far,保持far在满足正常需求情况下的最小值 + //far值:视点与地球切面的距离 + var length2EarthOrigin = Vector.fromVertice(this.getPosition()).getLength(); + var far = Math.sqrt(length2EarthOrigin * length2EarthOrigin - Kernel.EARTH_RADIUS * Kernel.EARTH_RADIUS); + far *= 1.05; + this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); } getViewMatrix(): Matrix { @@ -119,8 +133,14 @@ class PerspectiveCamera extends Object3D { return this.viewMatrix; } + updateProjMatrix(): Matrix{ + this._updateFar(); + return this.projMatrix; + } + updateProjViewMatrix(): Matrix{ this.updateViewMatrix(); + this.updateProjMatrix(); this.projViewMatrix = this.projMatrix.multiplyMatrix(this.viewMatrix); return this.projViewMatrix; } @@ -142,11 +162,13 @@ class PerspectiveCamera extends Object3D { 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); + // 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); + + this._updateFar(); } lookAt(targetPnt: Vertice, upDirection?: Vector): void { @@ -395,11 +417,6 @@ class PerspectiveCamera extends Object3D { var pNew = Vector.verticePlusVector(pOld, dir); this.setPosition(pNew.x, pNew.y, pNew.z); } - //重新计算far,保持far在满足正常需求情况下的最小值 - //far值:视点与地球切面的距离 - var length2EarthOrigin = Vector.fromVertice(this.getPosition()).getLength(); - var far = Math.sqrt(length2EarthOrigin * length2EarthOrigin - Kernel.EARTH_RADIUS * Kernel.EARTH_RADIUS); - this.setFar(far * 1.05); } //判断世界坐标系中的点是否在Canvas中可见 diff --git a/src/world/geometries/Marker.ts b/src/world/geometries/Marker.ts index 4f13c19..c76a9c0 100644 --- a/src/world/geometries/Marker.ts +++ b/src/world/geometries/Marker.ts @@ -15,7 +15,10 @@ class Marker implements Geometry{ this.vbo.unbind(); } - destroy(){} + destroy(){ + this.vbo.destroy(); + this.vbo = null; + } } export = Marker; \ No newline at end of file From 83b0fb6f3231da34a8ac74af37ed5e8b10523c27 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 25 Nov 2016 19:31:49 +0800 Subject: [PATCH 047/109] update depth --- src/world/Event.ts | 4 +- src/world/Globe.ts | 3 +- src/world/Kernel.ts | 2 +- src/world/PerspectiveCamera.ts | 33 ++++++---- src/world/Renderer.ts | 2 +- src/world/math/Math.ts | 116 +++++++++++++++++---------------- 6 files changed, 88 insertions(+), 72 deletions(-) diff --git a/src/world/Event.ts b/src/world/Event.ts index 6288c43..359d451 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -154,8 +154,8 @@ const EventModule = { } var newLevel = globe.CURRENT_LEVEL + deltaLevel; if(newLevel >= 0){ - //globe.setLevel(newLevel); - globe.animateToLevel(newLevel); + globe.setLevel(newLevel); + //globe.animateToLevel(newLevel); } }, diff --git a/src/world/Globe.ts b/src/world/Globe.ts index abb363d..51dbb22 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -20,8 +20,7 @@ class Globe { camera: PerspectiveCamera = null; tiledLayer: TiledLayer = null; - constructor(canvas: HTMLCanvasElement, args: any) { - args = args || {}; + constructor(canvas: HTMLCanvasElement) { Kernel.globe = this; this.renderer = Kernel.renderer = new Renderer(canvas); this.scene = new Scene(); diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index e3ad69d..e9eb22b 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -3,7 +3,7 @@ import {WebGLRenderingContextExtension} from './Definitions'; import Globe = require("./Globe"); import Renderer = require("./Renderer"); -const radius = 14000;//6378137 +const radius = 500;//6378137 const maxProjectedCoord = Math.PI * radius; const Kernel = { diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index 70a7694..7740b50 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -162,12 +162,6 @@ class PerspectiveCamera extends Object3D { 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); - this._updateFar(); } @@ -411,14 +405,31 @@ class PerspectiveCamera extends Object3D { } 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); + if(length2Surface > (this.near * 0.6)){ + 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); + }else{ + var deltaLevel = level - Kernel.globe.CURRENT_LEVEL; + var newFov = this._zoomInByFov(this.fov, deltaLevel) + this.setFov(newFov); + } } } + private _zoomInByFov(fov1: number, deltaLevel: number): number{ + var radianFov1 = MathUtils.degreeToRadian(fov1); + var halfRadianFov1 = radianFov1 / 2; + var tan1 = Math.tan(halfRadianFov1); + var tan2 = tan1 / Math.pow(2, deltaLevel); + var halfRadianFov2 = Math.atan(tan2); + var radianFov2 = halfRadianFov2 * 2; + var fov2 = MathUtils.radianToDegree(radianFov2); + return fov2; + } + //判断世界坐标系中的点是否在Canvas中可见 //options:projView、verticeInNDC isWorldVerticeVisibleInCanvas(verticeInWorld: Vertice, options?: any): boolean { diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 9b57df6..272a904 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -64,7 +64,7 @@ class Renderer { Kernel.gl.depthFunc(Kernel.gl.LEQUAL); Kernel.gl.depthMask(true); camera.viewMatrix = null; - //update viewMatrix and projViewMatrix of camera + //update viewMatrix, projMatrix and projViewMatrix of camera camera.updateProjViewMatrix(); scene.draw(camera); } diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index 2f21e1d..4e052ca 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -309,7 +309,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 @@ -322,7 +322,7 @@ const MathUtils = { } var canvasX = (1 + ndcX) * Kernel.canvas.width / 2.0; var canvasY = (1 - ndcY) * Kernel.canvas.height / 2.0; - return [canvasX,canvasY]; + return [canvasX, canvasY]; }, /** @@ -341,10 +341,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); @@ -353,10 +353,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); }, /** @@ -369,7 +369,7 @@ const MathUtils = { var x = verticeCopy.x; var y = verticeCopy.y; var z = verticeCopy.z; - var sin2 = y/Kernel.EARTH_RADIUS; + var sin2 = y / Kernel.EARTH_RADIUS; if(sin2 > 1){ sin2 = 2; }else if(sin2 < -1){ @@ -390,16 +390,22 @@ const MathUtils = { cos1 = -1; } var radianLog = Math.asin(sin1); - if(sin1 >= 0){//经度在[0,π] - if(cos1 >= 0){//经度在[0,π/2]之间 + 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; } } @@ -455,9 +461,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; }, @@ -501,7 +507,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; @@ -513,7 +519,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); @@ -526,10 +532,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; @@ -542,7 +548,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); @@ -558,7 +564,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]; }, /** @@ -570,13 +576,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; @@ -592,14 +598,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; }, @@ -611,19 +617,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; }, @@ -636,20 +642,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; }, @@ -660,15 +666,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 Date: Sat, 26 Nov 2016 13:00:05 +0800 Subject: [PATCH 048/109] update depth issue,#14 --- src/world/Event.ts | 6 +-- src/world/Globe.ts | 22 +++++----- src/world/Kernel.ts | 1 + src/world/PerspectiveCamera.ts | 73 +++++++++++++++++++++------------- src/world/Renderer.ts | 2 +- 5 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/world/Event.ts b/src/world/Event.ts index 359d451..bde3f66 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -123,13 +123,13 @@ const EventModule = { 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); + 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.CURRENT_LEVEL + 1); + globe.setLevel(globe.getLevel() + 1); this.moveLonLatToCanvas(lon, lat, absoluteX, absoluteY); } } @@ -152,7 +152,7 @@ const EventModule = { delta = event.detail; deltaLevel = -parseInt((delta / 3)); } - var newLevel = globe.CURRENT_LEVEL + deltaLevel; + var newLevel = globe.getLevel() + deltaLevel; if(newLevel >= 0){ globe.setLevel(newLevel); //globe.animateToLevel(newLevel); diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 51dbb22..81da9ab 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -10,11 +10,10 @@ import Tile = require("./graphics/Tile"); import ImageUtils = require("./Image"); import EventUtils = require("./Event"); -class Globe { - MAX_LEVEL: number = 14;//最大的渲染级别14 - CURRENT_LEVEL: number = -1; //当前渲染等级 +class Globe { REFRESH_INTERVAL: number = 300; //Globe自动刷新时间间隔,以毫秒为单位 idTimeOut: any = null; //refresh自定刷新的timeOut的handle + level: number = -1; //当前渲染等级 renderer: Renderer = null; scene: Scene = null; camera: PerspectiveCamera = null; @@ -31,7 +30,7 @@ class Globe { this.setLevel(0); this.renderer.setIfAutoRefresh(true); EventUtils.initLayout(); - } + } setTiledLayer(tiledLayer: TiledLayer) { clearTimeout(this.idTimeOut); @@ -75,13 +74,17 @@ class Globe { this.tick(); } + getLevel(){ + return this.level; + } + 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) { + level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 + if (level != this.getLevel()) { if (this.camera instanceof PerspectiveCamera) { //要先执行camera.setLevel,然后再刷新 this.camera.setLevel(level); @@ -96,8 +99,8 @@ class Globe { animateToLevel(level: number){ if(!this.isAnimating()){ - level = level > this.MAX_LEVEL ? this.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 - if(level !== this.CURRENT_LEVEL){ + level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 + if(level !== this.getLevel()){ this.camera.animateToLevel(level); } } @@ -138,7 +141,8 @@ class Globe { if (!this.tiledLayer || !this.scene || !this.camera) { return; } - var level = this.CURRENT_LEVEL + 3; + this.camera.update(); + var level = this.getLevel() + 3; this.tiledLayer.updateSubLayerCount(level); var projView = this.camera.getProjViewMatrix(); var options = { diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index e9eb22b..ec6c06f 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -13,6 +13,7 @@ const Kernel = { globe: null, idCounter: 0, //Object3D对象的唯一标识 BASE_LEVEL: 6, //渲染的基准层级 + MAX_LEVEL: 14,//最大的渲染级别 EARTH_RADIUS: radius, MAX_PROJECTED_COORD: maxProjectedCoord, proxy: "" diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index 7740b50..d07707d 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -86,7 +86,7 @@ class PerspectiveCamera extends Object3D { return projViewMatrix; } - setFov(fov: number): void { + _setFov(fov: number): void { if (!(fov > 0)) { throw "invalid fov:" + fov; } @@ -100,7 +100,7 @@ class PerspectiveCamera extends Object3D { this._setPerspectiveMatrix(this.fov, aspect, this.near, this.far); } - setNear(near: number): void { + _setNear(near: number): void { if (!(near > 0)) { throw "invalid near:" + near; } @@ -145,6 +145,41 @@ class PerspectiveCamera extends Object3D { return this.projViewMatrix; } + private _setVirtualPosition(virtualPosisition: Vertice){ + + } + + private _getVirtualPosition(): Vertice{ + return null; + } + + private _zoomInByFov(fov1: number, deltaLevel: number): number{ + // if(length2Surface > (this.near * 0.6)){ + // 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); + // }else{ + // var deltaLevel = level - Kernel.globe.CURRENT_LEVEL; + // var newFov = this._zoomInByFov(this.fov, deltaLevel) + // this._setFov(newFov); + // } + + var radianFov1 = MathUtils.degreeToRadian(fov1); + var halfRadianFov1 = radianFov1 / 2; + var tan1 = Math.tan(halfRadianFov1); + var tan2 = tan1 / Math.pow(2, deltaLevel); + var halfRadianFov2 = Math.atan(tan2); + var radianFov2 = halfRadianFov2 * 2; + var fov2 = MathUtils.radianToDegree(radianFov2); + return fov2; + } + + update():void{ + this.updateProjViewMatrix(); + } + look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { var cameraPntCopy = cameraPnt.clone(); var targetPntCopy = targetPnt.clone(); @@ -318,7 +353,7 @@ class PerspectiveCamera extends Object3D { animateToLevel(level: number): void { var newCamera = this._animateToLevel(level); this._animateToCamera(newCamera, () => { - Kernel.globe.CURRENT_LEVEL = level; + Kernel.globe.level = level; }); } @@ -385,7 +420,7 @@ class PerspectiveCamera extends Object3D { 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; + Kernel.globe.level = level; } //设置观察到的层级 @@ -393,6 +428,7 @@ class PerspectiveCamera extends Object3D { if (!(Utils.isNonNegativeInteger(level))) { throw "invalid level:" + level; } + var globe = Kernel.globe; var pOld = this.getPosition(); if (pOld.x === 0 && pOld.y === 0 && pOld.z === 0) { //初始设置camera @@ -403,33 +439,16 @@ class PerspectiveCamera extends Object3D { var newPosition = vector.getVertice(); this.look(newPosition, origin); } else { - var length2SurfaceNow = MathUtils.getLengthFromCamera2EarthSurface(Kernel.globe.CURRENT_LEVEL); + var length2SurfaceNow = MathUtils.getLengthFromCamera2EarthSurface(globe.getLevel()); var length2Surface = MathUtils.getLengthFromCamera2EarthSurface(level); - if(length2Surface > (this.near * 0.6)){ - 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); - }else{ - var deltaLevel = level - Kernel.globe.CURRENT_LEVEL; - var newFov = this._zoomInByFov(this.fov, deltaLevel) - this.setFov(newFov); - } + 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); } } - private _zoomInByFov(fov1: number, deltaLevel: number): number{ - var radianFov1 = MathUtils.degreeToRadian(fov1); - var halfRadianFov1 = radianFov1 / 2; - var tan1 = Math.tan(halfRadianFov1); - var tan2 = tan1 / Math.pow(2, deltaLevel); - var halfRadianFov2 = Math.atan(tan2); - var radianFov2 = halfRadianFov2 * 2; - var fov2 = MathUtils.radianToDegree(radianFov2); - return fov2; - } - //判断世界坐标系中的点是否在Canvas中可见 //options:projView、verticeInNDC isWorldVerticeVisibleInCanvas(verticeInWorld: Vertice, options?: any): boolean { diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 272a904..281c3f4 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -65,7 +65,7 @@ class Renderer { Kernel.gl.depthMask(true); camera.viewMatrix = null; //update viewMatrix, projMatrix and projViewMatrix of camera - camera.updateProjViewMatrix(); + camera.update(); scene.draw(camera); } From c1c9a3783e1b613fba3a61d2b2505f20cee7e014 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sat, 26 Nov 2016 15:10:52 +0800 Subject: [PATCH 049/109] disable ALWAYS for depth test --- src/world/Enum.ts | 4 ---- src/world/Globe.ts | 5 +++-- src/world/PerspectiveCamera.ts | 4 ++-- src/world/Renderer.ts | 2 +- src/world/layers/PoiLayer.ts | 3 ++- src/world/layers/TiledLayer.ts | 4 ++-- 6 files changed, 10 insertions(+), 12 deletions(-) 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/Globe.ts b/src/world/Globe.ts index 81da9ab..15c61a8 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -10,7 +10,7 @@ import Tile = require("./graphics/Tile"); import ImageUtils = require("./Image"); import EventUtils = require("./Event"); -class Globe { +class Globe { REFRESH_INTERVAL: number = 300; //Globe自动刷新时间间隔,以毫秒为单位 idTimeOut: any = null; //refresh自定刷新的timeOut的handle level: number = -1; //当前渲染等级 @@ -30,7 +30,7 @@ class Globe { this.setLevel(0); this.renderer.setIfAutoRefresh(true); EventUtils.initLayout(); - } + } setTiledLayer(tiledLayer: TiledLayer) { clearTimeout(this.idTimeOut); @@ -141,6 +141,7 @@ class Globe { if (!this.tiledLayer || !this.scene || !this.camera) { return; } + //先更新camera中的各种矩阵 this.camera.update(); var level = this.getLevel() + 3; this.tiledLayer.updateSubLayerCount(level); diff --git a/src/world/PerspectiveCamera.ts b/src/world/PerspectiveCamera.ts index d07707d..fb7f830 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/PerspectiveCamera.ts @@ -165,7 +165,7 @@ class PerspectiveCamera extends Object3D { // var newFov = this._zoomInByFov(this.fov, deltaLevel) // this._setFov(newFov); // } - + var radianFov1 = MathUtils.degreeToRadian(fov1); var halfRadianFov1 = radianFov1 / 2; var tan1 = Math.tan(halfRadianFov1); @@ -445,7 +445,7 @@ class PerspectiveCamera extends Object3D { var dir = this.getLightDirection(); dir.setLength(deltaLength); var pNew = Vector.verticePlusVector(pOld, dir); - this.setPosition(pNew.x, pNew.y, pNew.z); + this.setPosition(pNew.x, pNew.y, pNew.z); } } diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 281c3f4..8144331 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -64,7 +64,7 @@ class Renderer { Kernel.gl.depthFunc(Kernel.gl.LEQUAL); Kernel.gl.depthMask(true); camera.viewMatrix = null; - //update viewMatrix, projMatrix and projViewMatrix of camera + //update viewMatrix, projMatrix and projViewMatrix camera.update(); scene.draw(camera); } diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts index 214b7d1..404f40e 100644 --- a/src/world/layers/PoiLayer.ts +++ b/src/world/layers/PoiLayer.ts @@ -13,7 +13,8 @@ class PoiLayer extends GraphicGroup{ constructor(){ super(); - var p = MathUtils.geographicToCartesianCoord(116.408540, 39.902350, Kernel.EARTH_RADIUS + 0.001); + //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); diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index 2892845..5b03ccc 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -9,10 +9,10 @@ abstract class TiledLayer extends GraphicGroup { //重写 draw(camera: PerspectiveCamera){ //此处将深度测试设置为ALWAYS是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 - Kernel.gl.depthFunc(Kernel.gl.ALWAYS); + //Kernel.gl.depthFunc(Kernel.gl.ALWAYS); super.draw(camera); //将深度测试恢复成LEQUAL - Kernel.gl.depthFunc(Kernel.gl.LEQUAL); + //Kernel.gl.depthFunc(Kernel.gl.LEQUAL); } //重写 From 067221aff865b68f8986bdcdd66fd1674040f542 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sat, 26 Nov 2016 15:14:52 +0800 Subject: [PATCH 050/109] rename PerspectiveCamera to Camera --- src/world/{PerspectiveCamera.ts => Camera.ts} | 14 +++++++------- src/world/Event.ts | 4 ++-- src/world/Globe.ts | 8 ++++---- src/world/GraphicGroup.ts | 4 ++-- src/world/Renderer.ts | 8 ++++---- src/world/graphics/Graphic.ts | 6 +++--- src/world/graphics/MeshGraphic.ts | 4 ++-- src/world/graphics/Poi.ts | 4 ++-- src/world/layers/PoiLayer.ts | 1 - src/world/layers/TiledLayer.ts | 4 ++-- 10 files changed, 28 insertions(+), 29 deletions(-) rename src/world/{PerspectiveCamera.ts => Camera.ts} (98%) diff --git a/src/world/PerspectiveCamera.ts b/src/world/Camera.ts similarity index 98% rename from src/world/PerspectiveCamera.ts rename to src/world/Camera.ts index fb7f830..b85fd5e 100644 --- a/src/world/PerspectiveCamera.ts +++ b/src/world/Camera.ts @@ -1,4 +1,4 @@ -/// +/// import Kernel = require('./Kernel'); import Utils = require('./Utils'); import MathUtils = require('./math/Math'); @@ -10,7 +10,7 @@ import TileGrid = require('./TileGrid'); import Matrix = require('./math/Matrix'); import Object3D = require('./Object3D'); -class PerspectiveCamera extends Object3D { +class Camera extends Object3D { private animationDuration = 600;//层级变化的动画周期是600毫秒 pitch: number; viewMatrix: Matrix; @@ -357,7 +357,7 @@ class PerspectiveCamera extends Object3D { }); } - private _animateToCamera(newCamera: PerspectiveCamera, cb: ()=>void){ + private _animateToCamera(newCamera: Camera, cb: ()=>void){ if(this.isAnimating()){ return; } @@ -389,7 +389,7 @@ class PerspectiveCamera extends Object3D { requestAnimationFrame(callback); } - private _animateToLevel(level: number): PerspectiveCamera{ + private _animateToLevel(level: number): Camera{ if (!(Utils.isNonNegativeInteger(level))) { throw "invalid level:" + level; } @@ -399,8 +399,8 @@ class PerspectiveCamera extends Object3D { return camera; } - private _clone(): PerspectiveCamera{ - var camera: PerspectiveCamera = new PerspectiveCamera(); + private _clone(): Camera{ + var camera: Camera = new Camera(); (Object).assign(camera, this.toJson()); return camera; } @@ -793,4 +793,4 @@ class PerspectiveCamera extends Object3D { } } -export = PerspectiveCamera; \ No newline at end of file +export = Camera; \ No newline at end of file diff --git a/src/world/Event.ts b/src/world/Event.ts index bde3f66..1c7b9ec 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -2,7 +2,7 @@ import Kernel = require("./Kernel"); import MathUtils = require("./math/Math"); import Vector = require("./math/Vector"); -import PerspectiveCamera = require("./PerspectiveCamera"); +import Camera = require("./Camera"); type MouseMoveListener = (e: MouseEvent) => {}; @@ -102,7 +102,7 @@ const EventModule = { var v2 = Vector.fromVertice(p2); var rotateVector = v1.cross(v2); var rotateRadian = -Vector.getRadianOfTwoVectors(v1, v2); - var camera: PerspectiveCamera = Kernel.globe.camera; + var camera: Camera = Kernel.globe.camera; camera.worldRotateByVector(rotateRadian, rotateVector); }, diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 15c61a8..ea6a40a 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -2,7 +2,7 @@ import Kernel = require("./Kernel"); import Utils = require("./Utils"); import Renderer = require("./Renderer"); -import PerspectiveCamera = require("./PerspectiveCamera"); +import Camera = require("./Camera"); import Scene = require("./Scene"); import TiledLayer = require("./layers/TiledLayer"); import SubTiledLayer = require("./layers/SubTiledLayer"); @@ -16,7 +16,7 @@ class Globe { level: number = -1; //当前渲染等级 renderer: Renderer = null; scene: Scene = null; - camera: PerspectiveCamera = null; + camera: Camera = null; tiledLayer: TiledLayer = null; constructor(canvas: HTMLCanvasElement) { @@ -24,7 +24,7 @@ class Globe { this.renderer = Kernel.renderer = new Renderer(canvas); this.scene = new Scene(); var radio = canvas.width / canvas.height; - this.camera = new PerspectiveCamera(30, radio, 1, Kernel.EARTH_RADIUS * 2); + this.camera = new Camera(30, radio, 1, Kernel.EARTH_RADIUS * 2); this.renderer.setScene(this.scene); this.renderer.setCamera(this.camera); this.setLevel(0); @@ -85,7 +85,7 @@ class Globe { level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 if (level != this.getLevel()) { - if (this.camera instanceof PerspectiveCamera) { + if (this.camera instanceof Camera) { //要先执行camera.setLevel,然后再刷新 this.camera.setLevel(level); this.refresh(); diff --git a/src/world/GraphicGroup.ts b/src/world/GraphicGroup.ts index f7b6f64..83bef4e 100644 --- a/src/world/GraphicGroup.ts +++ b/src/world/GraphicGroup.ts @@ -1,7 +1,7 @@ /// import Kernel = require("./Kernel"); import Graphic = require("./graphics/Graphic"); -import PerspectiveCamera = require("./PerspectiveCamera"); +import Camera = require("./Camera"); type Drawable = Graphic | GraphicGroup; @@ -69,7 +69,7 @@ class GraphicGroup{ return this.visible; } - draw(camera: PerspectiveCamera){ + draw(camera: Camera){ if(this.isDrawable()){ this.children.forEach(function(g: Drawable){ if(g.isDrawable()){ diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 8144331..378d7c2 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -2,12 +2,12 @@ import Kernel = require("./Kernel"); import EventUtils = require("./Event"); import Scene = require("./Scene"); -import PerspectiveCamera = require("./PerspectiveCamera"); +import Camera = require("./Camera"); import {WebGLRenderingContextExtension, WebGLProgramExtension} from "./Definitions"; class Renderer { scene: Scene = null; - camera: PerspectiveCamera = null; + camera: Camera = null; bAutoRefresh: boolean = false; constructor(canvas: HTMLCanvasElement) { @@ -57,7 +57,7 @@ class Renderer { //gl.enable(gl.TEXTURE_2D);//WebGL: INVALID_ENUM: enable: invalid capability } - render(scene: Scene, camera: PerspectiveCamera) { + render(scene: Scene, camera: Camera) { Kernel.gl.viewport(0, 0, Kernel.canvas.width, Kernel.canvas.height); Kernel.gl.clear(Kernel.gl.COLOR_BUFFER_BIT | Kernel.gl.DEPTH_BUFFER_BIT); Kernel.gl.enable(Kernel.gl.DEPTH_TEST); @@ -73,7 +73,7 @@ class Renderer { this.scene = scene; } - setCamera(camera: PerspectiveCamera) { + setCamera(camera: Camera) { this.camera = camera; } diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts index 392349f..af08ed8 100644 --- a/src/world/graphics/Graphic.ts +++ b/src/world/graphics/Graphic.ts @@ -5,7 +5,7 @@ import Geometry = require("../geometries/Geometry"); import Material = require("../materials/Material"); import Program = require("../Program"); import ProgramUtils = require("../ProgramUtils"); -import PerspectiveCamera = require("../PerspectiveCamera"); +import Camera = require("../Camera"); interface GraphicOptions{ geometry: Geometry; @@ -44,14 +44,14 @@ abstract class Graphic{ return this.visible && this.isReady(); } - draw(camera: PerspectiveCamera){ + draw(camera: Camera){ if(this.isDrawable()){ this.program.use(); this.onDraw(camera); } } - abstract onDraw(camera: PerspectiveCamera):void + abstract onDraw(camera: Camera):void destroy(){ this.parent = null; diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index b94b888..0005ac1 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -5,7 +5,7 @@ import Program = require("../Program"); import Graphic = require("./Graphic"); import Mesh = require("../geometries/Mesh"); import MeshTextureMaterial = require("../materials/MeshTextureMaterial"); -import PerspectiveCamera = require("../PerspectiveCamera"); +import Camera = require("../Camera"); const vs = ` @@ -70,7 +70,7 @@ class MeshGraphic extends Graphic { gl.uniform1i(locSampler, 0); } - onDraw(camera: PerspectiveCamera) { + onDraw(camera: Camera) { var gl = Kernel.gl; //aPosition diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index 8b741f9..50c7f20 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -5,7 +5,7 @@ import Graphic = require('./Graphic'); import Marker = require('../geometries/Marker'); import PoiMaterial = require('../materials/PoiMaterial'); import Program = require("../Program"); -import PerspectiveCamera = require("../PerspectiveCamera"); +import Camera = require("../Camera"); const vs = ` @@ -40,7 +40,7 @@ class Poi extends Graphic { return new Program(this.getProgramType(), vs, fs); } - onDraw(camera: PerspectiveCamera){ + onDraw(camera: Camera){ var gl = Kernel.gl; //gl.disable(gl.DEPTH_TEST); diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts index 404f40e..2744c60 100644 --- a/src/world/layers/PoiLayer.ts +++ b/src/world/layers/PoiLayer.ts @@ -6,7 +6,6 @@ import GraphicGroup = require('../GraphicGroup'); import Poi = require('../graphics/Poi'); import Marker = require('../geometries/Marker'); import PoiMaterial = require('../materials/PoiMaterial'); -import PerspectiveCamera = require('../PerspectiveCamera'); import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); class PoiLayer extends GraphicGroup{ diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index 5b03ccc..5dadd3a 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -2,12 +2,12 @@ import Kernel = require('../Kernel'); import GraphicGroup = require('../GraphicGroup'); import SubTiledLayer = require('./SubTiledLayer'); -import PerspectiveCamera = require('../PerspectiveCamera'); +import Camera = require('../Camera'); abstract class TiledLayer extends GraphicGroup { //重写 - draw(camera: PerspectiveCamera){ + draw(camera: Camera){ //此处将深度测试设置为ALWAYS是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 //Kernel.gl.depthFunc(Kernel.gl.ALWAYS); super.draw(camera); From 9234d1b084e316313c462c8dd0774f113adf7dd9 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sat, 26 Nov 2016 18:34:04 +0800 Subject: [PATCH 051/109] update Camera,#14 --- src/world/Camera.ts | 352 +++++++++++++++++++++++------------------ src/world/Globe.ts | 16 +- src/world/math/Math.ts | 9 -- 3 files changed, 197 insertions(+), 180 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index b85fd5e..e1d10cf 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -11,7 +11,13 @@ import Matrix = require('./math/Matrix'); import Object3D = require('./Object3D'); class Camera extends Object3D { - private animationDuration = 600;//层级变化的动画周期是600毫秒 + private readonly animationDuration: number = 600;//层级变化的动画周期是600毫秒 + private readonly nearFactor: number = 0.6; + private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; + + deltaFovLevel: number = 0; + thresholdLevelForNear: number = -1; + level: number = -1; //当前渲染等级 pitch: number; viewMatrix: Matrix; projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far @@ -22,19 +28,23 @@ class Camera extends Object3D { }; private animating: boolean = false; - constructor(public fov = 45, public aspect = 1, public near = 1, public far = 100) { + //this.near一旦初始化之后就不应该再修改 + //this.far可以动态计算 + //this.aspect在Viewport改变后重新计算 + //this.fov可以调整以实现缩放效果 + constructor(private fov = 45, private aspect = 1, private near = 1, private far = 100) { super(); this.pitch = 90; this.projMatrix = new Matrix(); this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); } - _setPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { + private _setPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { this._rawSetPerspectiveMatrix(fov, aspect, near, far); this._updateFar(); } - _rawSetPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { + private _rawSetPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { //https://github.com/toji/gl-matrix/blob/master/src/gl-matrix/mat4.js#L1788 this.fov = fov; this.aspect = aspect; @@ -86,7 +96,7 @@ class Camera extends Object3D { return projViewMatrix; } - _setFov(fov: number): void { + private _setFov(fov: number): void { if (!(fov > 0)) { throw "invalid fov:" + fov; } @@ -100,21 +110,7 @@ class Camera extends Object3D { 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._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); - // } - - _updateFar():void{ + private _updateFar(): void { //重新计算far,保持far在满足正常需求情况下的最小值 //far值:视点与地球切面的距离 var length2EarthOrigin = Vector.fromVertice(this.getPosition()).getLength(); @@ -128,56 +124,198 @@ class Camera extends Object3D { return this.matrix.getInverseMatrix(); } - updateViewMatrix(): Matrix{ + update(): void { + //通过修改position和fov以更新matrix和projMatrix + //this._updatePositionAndFov(); + + //在_updatePositionAndFov()方法调用之后再计算viewMatrix this.viewMatrix = this.getViewMatrix(); - return this.viewMatrix; - } - updateProjMatrix(): Matrix{ + //最后更新far this._updateFar(); - return this.projMatrix; - } - updateProjViewMatrix(): Matrix{ - this.updateViewMatrix(); - this.updateProjMatrix(); + //update projViewMatrix this.projViewMatrix = this.projMatrix.multiplyMatrix(this.viewMatrix); - return this.projViewMatrix; } - private _setVirtualPosition(virtualPosisition: Vertice){ + //计算从第几级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); + } + /** + * 根据层级计算出摄像机应该放置到距离地球表面多远的位置 + * @param level + * @return {*} + */ + private _getTheoryDistanceFromCamera2EarthSurface(level: number): number { + return this.baseTheoryDistanceFromCamera2EarthSurface / Math.pow(2, level); } - private _getVirtualPosition(): Vertice{ + private _setVirtualPosition(virtualPosisition: Vertice) { + + } + + private _getVirtualPosition(): Vertice { return null; } - private _zoomInByFov(fov1: number, deltaLevel: number): number{ - // if(length2Surface > (this.near * 0.6)){ - // 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); - // }else{ - // var deltaLevel = level - Kernel.globe.CURRENT_LEVEL; - // var newFov = this._zoomInByFov(this.fov, deltaLevel) - // this._setFov(newFov); - // } + //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov + private _updatePositionAndFov(): number{ + // var safeThresholdLevel = this._getSafeThresholdLevelForNear(); + // if(this.getLevel() > safeThresholdLevel){ + + // } + var distance2EarthSurface = this.getDistance2EarthSurface(); + var thresholdNear = this.near * this.nearFactor; + if(distance2EarthSurface <= thresholdNear){ + //摄像机距离地球太近,导致图层不满足视景体的near值 + //我们需要将摄像机的位置拉远,以满足near值 + var safeLevel = this._getSafeThresholdLevelForNear(); + var deltaLevel = this.getLevel() - safeLevel; + if(deltaLevel !== 0){ + this._rawSetLevel(deltaLevel); + //摄像机位置拉远之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 + var newFov = this._calculateFovByDeltaLevel(this.fov, deltaLevel); + this._setFov(newFov); + return this.fov; + } + } + return -1; + } + + //通过调整fov的值造成层级缩放的效果 + private _calculateFovByDeltaLevel(oldFov: number, deltaLevel: number): number { + //tan(halfFov) = h / distance + 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); + this.deltaFovLevel += deltaLevel; + return newFov; + } + + getLevel(): number { + return this.level; + } + + setLevel(level: number): void { + var isLevelChanged = this._rawSetLevel(level); + if (isLevelChanged) { + //不要在this._setLevel()方法中更新this.level,因为这会影响animateToLevel()方法 + this.level = level; + Kernel.globe.refresh(); + } + } + + //设置观察到的层级,不要在该方法中修改this.level的值 + private _rawSetLevel(level: number): boolean { + if (!(Utils.isNonNegativeInteger(level))) { + throw "invalid level:" + level; + } + level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 + if (level === this.level) { + return false; + } + var globe = Kernel.globe; + var pOld = this.getPosition(); + if (pOld.x === 0 && pOld.y === 0 && pOld.z === 0) { + //初始设置camera + var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 + //var newPosition = MathUtils.geographicToCartesianCoord(115, 0, Kernel.EARTH_RADIUS + length) + 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 distance2SurfaceNow = this._getTheoryDistanceFromCamera2EarthSurface(this.getLevel()); + var distance2SurfaceNew = this._getTheoryDistanceFromCamera2EarthSurface(level); + var deltaDistance = distance2SurfaceNow - distance2SurfaceNew; + var dir = this.getLightDirection(); + dir.setLength(deltaDistance); + var pNew = Vector.verticePlusVector(pOld, dir); + this.setPosition(pNew.x, pNew.y, pNew.z); + } + return true; + } + + isAnimating(): boolean { + return this.animating; + } + + animateToLevel(level: number): void { + var newCamera = this._animateToLevel(level); + this._animateToCamera(newCamera, () => { + this.level = level; + }); + } + + private _animateToCamera(newCamera: Camera, cb: () => void) { + if (this.isAnimating()) { + return; + } + this.animating = true; + var oldPosition = this.getPosition(); + var newPosition = newCamera.matrix.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 start: number = -1; + var callback = (timestap: number) => { + if (start < 0) { + start = timestap; + } + var a = timestap - start; + if (a >= span) { + (Object).assign(this, newCamera.toJson()); + this.animating = false; + cb(); + } else { + var p = this.getPosition(); + this.setPosition(p.x + deltaX, p.y + deltaY, p.z + deltaZ); + requestAnimationFrame(callback); + } + }; + requestAnimationFrame(callback); + } - var radianFov1 = MathUtils.degreeToRadian(fov1); - var halfRadianFov1 = radianFov1 / 2; - var tan1 = Math.tan(halfRadianFov1); - var tan2 = tan1 / Math.pow(2, deltaLevel); - var halfRadianFov2 = Math.atan(tan2); - var radianFov2 = halfRadianFov2 * 2; - var fov2 = MathUtils.radianToDegree(radianFov2); - return fov2; + private _animateToLevel(level: number): Camera { + if (!(Utils.isNonNegativeInteger(level))) { + throw "invalid level:" + level; + } + var camera = this._clone(); + //don't call setLevel method because it will update CURRENT_LEVEL + camera._rawSetLevel(level); + return camera; } - update():void{ - this.updateProjViewMatrix(); + private _clone(): Camera { + var camera: Camera = new Camera(); + (Object).assign(camera, this.toJson()); + return camera; + } + + toJson(): any { + return { + pitch: this.pitch, + near: this.near, + far: this.far, + fov: this.fov, + aspect: this.aspect, + matrix: this.matrix.clone(), + projMatrix: this.projMatrix.clone() + }; } look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { @@ -346,109 +484,6 @@ class Camera extends Object3D { return plan; } - isAnimating(): boolean{ - return this.animating; - } - - animateToLevel(level: number): void { - var newCamera = this._animateToLevel(level); - this._animateToCamera(newCamera, () => { - Kernel.globe.level = level; - }); - } - - private _animateToCamera(newCamera: Camera, cb: ()=>void){ - if(this.isAnimating()){ - return; - } - this.animating = true; - var oldPosition = this.getPosition(); - var newPosition = newCamera.matrix.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 start:number = -1; - var callback = (timestap: number) => { - if(start < 0){ - start = timestap; - } - var a = timestap - start; - if(a >= span){ - (Object).assign(this, newCamera.toJson()); - 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): Camera{ - 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; - } - - private _clone(): Camera{ - var camera: Camera = new Camera(); - (Object).assign(camera, this.toJson()); - return camera; - } - - toJson(): any { - return { - pitch: this.pitch, - near: this.near, - far: this.far, - fov: this.fov, - aspect: this.aspect, - matrix: this.matrix.clone(), - projMatrix: this.projMatrix.clone() - }; - } - - setLevel(level: number): void{ - this._setLevel(level); - //don't update CURRENT_LEVEL in _setLevel method because it will affect animateToLevel method - Kernel.globe.level = level; - } - - //设置观察到的层级 - private _setLevel(level: number): void { - if (!(Utils.isNonNegativeInteger(level))) { - throw "invalid level:" + level; - } - var globe = Kernel.globe; - 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(globe.getLevel()); - 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 { @@ -520,7 +555,8 @@ class Camera extends Object3D { return false; } - function handleRow(centerRow: number, centerColumn: number) { + //处理一整行 + 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); diff --git a/src/world/Globe.ts b/src/world/Globe.ts index ea6a40a..ec6a4d9 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -13,7 +13,6 @@ import EventUtils = require("./Event"); class Globe { REFRESH_INTERVAL: number = 300; //Globe自动刷新时间间隔,以毫秒为单位 idTimeOut: any = null; //refresh自定刷新的timeOut的handle - level: number = -1; //当前渲染等级 renderer: Renderer = null; scene: Scene = null; camera: Camera = null; @@ -75,21 +74,12 @@ class Globe { } getLevel(){ - return this.level; + return this.camera ? this.camera.getLevel() : -1; } setLevel(level: number) { - if (!Utils.isNonNegativeInteger(level)) { - throw "invalid level:" + level; - } - - level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 - if (level != this.getLevel()) { - if (this.camera instanceof Camera) { - //要先执行camera.setLevel,然后再刷新 - this.camera.setLevel(level); - this.refresh(); - } + if(this.camera){ + this.camera.setLevel(level); } } diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index 4e052ca..e72b5f5 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -325,15 +325,6 @@ const MathUtils = { return [canvasX, canvasY]; }, - /** - * 根据层级计算出摄像机应该放置到距离地球表面多远的位置 - * @param level - * @return {*} - */ - getLengthFromCamera2EarthSurface(level: number): number{ - return 1.23 * Kernel.EARTH_RADIUS / Math.pow(2, level); - }, - /**将经纬度转换为笛卡尔空间直角坐标系中的x、y、z * @lon 经度(角度单位) * @lat 纬度(角度单位) From e69fe965ee4315853aededd72a8aa9e7d7d07cee Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sat, 26 Nov 2016 18:35:03 +0800 Subject: [PATCH 052/109] update,#14 --- src/world/Camera.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index e1d10cf..80a220b 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -229,7 +229,6 @@ class Camera extends Object3D { if (pOld.x === 0 && pOld.y === 0 && pOld.z === 0) { //初始设置camera var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 - //var newPosition = MathUtils.geographicToCartesianCoord(115, 0, Kernel.EARTH_RADIUS + length) var origin = new Vertice(0, 0, 0); var vector = this.getLightDirection().getOpposite(); vector.setLength(length); From 01d41eb63032c0eef2ff44c8c93f9fc5eee7666a Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sat, 26 Nov 2016 19:12:04 +0800 Subject: [PATCH 053/109] make _updatePositionAndFov basically work,#14 --- src/world/Camera.ts | 12 +++++++----- src/world/layers/PoiLayer.ts | 4 ++-- src/world/layers/TiledLayer.ts | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 80a220b..9e551c1 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -125,8 +125,10 @@ class Camera extends Object3D { } update(): void { + this.viewMatrix = null; + //通过修改position和fov以更新matrix和projMatrix - //this._updatePositionAndFov(); + this._updatePositionAndFov(); //在_updatePositionAndFov()方法调用之后再计算viewMatrix this.viewMatrix = this.getViewMatrix(); @@ -178,7 +180,7 @@ class Camera extends Object3D { var safeLevel = this._getSafeThresholdLevelForNear(); var deltaLevel = this.getLevel() - safeLevel; if(deltaLevel !== 0){ - this._rawSetLevel(deltaLevel); + this._rawUpdatePositionByLevel(safeLevel); //摄像机位置拉远之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 var newFov = this._calculateFovByDeltaLevel(this.fov, deltaLevel); this._setFov(newFov); @@ -207,7 +209,7 @@ class Camera extends Object3D { } setLevel(level: number): void { - var isLevelChanged = this._rawSetLevel(level); + var isLevelChanged = this._rawUpdatePositionByLevel(level); if (isLevelChanged) { //不要在this._setLevel()方法中更新this.level,因为这会影响animateToLevel()方法 this.level = level; @@ -216,7 +218,7 @@ class Camera extends Object3D { } //设置观察到的层级,不要在该方法中修改this.level的值 - private _rawSetLevel(level: number): boolean { + private _rawUpdatePositionByLevel(level: number): boolean { if (!(Utils.isNonNegativeInteger(level))) { throw "invalid level:" + level; } @@ -295,7 +297,7 @@ class Camera extends Object3D { } var camera = this._clone(); //don't call setLevel method because it will update CURRENT_LEVEL - camera._rawSetLevel(level); + camera._rawUpdatePositionByLevel(level); return camera; } diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts index 2744c60..db95d1e 100644 --- a/src/world/layers/PoiLayer.ts +++ b/src/world/layers/PoiLayer.ts @@ -12,8 +12,8 @@ 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 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); diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index 5dadd3a..d1bd0ac 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -9,10 +9,10 @@ abstract class TiledLayer extends GraphicGroup { //重写 draw(camera: Camera){ //此处将深度测试设置为ALWAYS是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 - //Kernel.gl.depthFunc(Kernel.gl.ALWAYS); + Kernel.gl.depthFunc(Kernel.gl.ALWAYS); super.draw(camera); //将深度测试恢复成LEQUAL - //Kernel.gl.depthFunc(Kernel.gl.LEQUAL); + Kernel.gl.depthFunc(Kernel.gl.LEQUAL); } //重写 From 8d433c0b1861bf3269ea1fb78183a49c945be808 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sat, 26 Nov 2016 22:10:16 +0800 Subject: [PATCH 054/109] fix globe.tick() bug and make camera._updatePositionAndFov() work,#14 --- src/world/Camera.ts | 131 +++++++++++++++++++++++++++++++++++--------- src/world/Globe.ts | 7 ++- 2 files changed, 111 insertions(+), 27 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 9e551c1..c99d696 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -11,10 +11,10 @@ import Matrix = require('./math/Matrix'); import Object3D = require('./Object3D'); class Camera extends Object3D { + private readonly initFov: number; private readonly animationDuration: number = 600;//层级变化的动画周期是600毫秒 private readonly nearFactor: number = 0.6; private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; - deltaFovLevel: number = 0; thresholdLevelForNear: number = -1; level: number = -1; //当前渲染等级 @@ -34,6 +34,7 @@ class Camera extends Object3D { //this.fov可以调整以实现缩放效果 constructor(private fov = 45, private aspect = 1, private near = 1, private far = 100) { super(); + this.initFov = this.fov; this.pitch = 90; this.projMatrix = new Matrix(); this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); @@ -142,7 +143,7 @@ class Camera extends Object3D { //计算从第几级level开始不满足视景体的near值 //比如第10级满足near,第11级不满足near,那么返回10 - private _getSafeThresholdLevelForNear(){ + private _getSafeThresholdLevelForNear() { var thresholdNear = this.near * this.nearFactor; var pow2level = this.baseTheoryDistanceFromCamera2EarthSurface / thresholdNear; var level = (Math).log2(pow2level); @@ -167,32 +168,104 @@ class Camera extends Object3D { } //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov - private _updatePositionAndFov(): number{ + private _updatePositionAndFov(): number { + //是否满足near值,和fov没有关系,和position有关 + //但是改变position的话,fov也要相应变动以满足对应的缩放效果 + // var safeThresholdLevel = this._getSafeThresholdLevelForNear(); // if(this.getLevel() > safeThresholdLevel){ // } - var distance2EarthSurface = this.getDistance2EarthSurface(); - var thresholdNear = this.near * this.nearFactor; - if(distance2EarthSurface <= thresholdNear){ - //摄像机距离地球太近,导致图层不满足视景体的near值 + const currentLevel = this.getLevel(); + + var safeLevel = this._getSafeThresholdLevelForNear(); + + if(currentLevel > safeLevel){ + //摄像机距离地球太近,导致不满足视景体的near值, //我们需要将摄像机的位置拉远,以满足near值 - var safeLevel = this._getSafeThresholdLevelForNear(); - var deltaLevel = this.getLevel() - safeLevel; - if(deltaLevel !== 0){ - this._rawUpdatePositionByLevel(safeLevel); - //摄像机位置拉远之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 - var newFov = this._calculateFovByDeltaLevel(this.fov, deltaLevel); - this._setFov(newFov); - return this.fov; - } - } + this._rawUpdatePositionByLevel(safeLevel); + //比如safeLevel是10,而currentLevel是11,则deltaLevel为1 + var deltaLevel = currentLevel - safeLevel; + //摄像机位置与地球表面距离变大之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 + //deltaLevel应该为正正数,计算出的newFov应该比this.initFov要小 + var newFov = this._calculateFovByDeltaLevel(this.initFov, deltaLevel); + this._setFov(newFov); + }else{ + this._rawUpdatePositionByLevel(currentLevel); + this._setFov(this.initFov); + } + + // var distance2EarthSurface = this.getDistance2EarthSurface(); + // var thresholdNear = this.near * this.nearFactor; + // if (distance2EarthSurface <= thresholdNear) { + // //摄像机距离地球太近,导致不满足视景体的near值 + // //我们需要将摄像机的位置拉远,以满足near值 + + // //比如currentLevel为11,safeLevel为10 + // deltaLevel = currentLevel - safeLevel; + // //此处的deltaLevel应该为正数 + // if (deltaLevel !== 0) { + // newLevel = safeLevel; + // this._rawUpdatePositionByLevel(newLevel); + // //摄像机位置与地球表面距离变大之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 + // var newFov = this._calculateFovByDeltaLevel(this.initFov, deltaLevel); + // this._setFov(newFov); + // return this.fov; + // } + // } else { + // //现在满足near值 + + // if (this.fov < this.initFov * 0.95 && currentLevel <= safeLevel) { + // //如果当前fov的值比初始的initFov值小,说明当前的fov被缩放过(0.95用于精度问题,防止出现29.999999与30进行对比的情况) + // //比如currentLevel为11,this.fov为15,this.initFov为10,deltaLevel应该为负值 + // deltaLevel = this._calculateDeltaLevelByFov(this.fov, this.initFov); + // newLevel = currentLevel + deltaLevel; + // //上面的newLevel是float类型,向上取整,不要向下取整 + // newLevel = Math.ceil(newLevel); + // //deltaLevel应该为负整数 + // deltaLevel = newLevel - currentLevel; + // //比如currentLevel为11,newLevel为10,deltaLevel为-1 + // this._rawUpdatePositionByLevel(newLevel); + // var newFov = this._calculateFovByDeltaLevel(this.fov, deltaLevel); + // this._setFov(newFov); + // return this.fov; + // } else if (this.fov > this.initFov * 1.05) { + // //不应该出现当前fov值比初始initFov的大的情况 + // throw `_updatePositionAndFov() Invalid fov: ${this.fov}`; + // } + //} + return -1; } - //通过调整fov的值造成层级缩放的效果 + + //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 + //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); @@ -237,13 +310,19 @@ class Camera extends Object3D { var newPosition = vector.getVertice(); this.look(newPosition, origin); } else { - var distance2SurfaceNow = this._getTheoryDistanceFromCamera2EarthSurface(this.getLevel()); - var distance2SurfaceNew = this._getTheoryDistanceFromCamera2EarthSurface(level); - var deltaDistance = distance2SurfaceNow - distance2SurfaceNew; - var dir = this.getLightDirection(); - dir.setLength(deltaDistance); - var pNew = Vector.verticePlusVector(pOld, dir); - this.setPosition(pNew.x, pNew.y, pNew.z); + var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 + var vector = this.getLightDirection().getOpposite(); + vector.setLength(length); + var newPosition = vector.getVertice(); + this.setPosition(newPosition.x, newPosition.y, newPosition.z); + + // var distance2SurfaceNow = this._getTheoryDistanceFromCamera2EarthSurface(this.getLevel()); + // var distance2SurfaceNew = this._getTheoryDistanceFromCamera2EarthSurface(level); + // var deltaDistance = distance2SurfaceNow - distance2SurfaceNew; + // var dir = this.getLightDirection(); + // dir.setLength(deltaDistance); + // var pNew = Vector.verticePlusVector(pOld, dir); + // this.setPosition(pNew.x, pNew.y, pNew.z); } return true; } diff --git a/src/world/Globe.ts b/src/world/Globe.ts index ec6a4d9..ed1b827 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -122,7 +122,12 @@ class Globe { tick() { var globe = Kernel.globe; if (globe) { - globe.refresh(); + try{ + //如果refresh方法出现异常而且没有捕捉,那么就会导致无法继续设置setTimeout,从而无法进一步更新切片 + globe.refresh(); + }catch(e){ + console.error(e); + } this.idTimeOut = setTimeout(globe.tick, globe.REFRESH_INTERVAL); } } From ae928a5e9e2339ef05a972dc7637be3318c7bcc6 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sun, 27 Nov 2016 01:17:54 +0800 Subject: [PATCH 055/109] add setPitch() and getPitch() method for camera,#14 --- src/world/Camera.ts | 12 ++++++++++-- src/world/Event.ts | 6 +++--- src/world/Globe.ts | 3 +-- src/world/Kernel.ts | 18 +++++++++--------- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index c99d696..3d1293f 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -15,10 +15,10 @@ class Camera extends Object3D { private readonly animationDuration: number = 600;//层级变化的动画周期是600毫秒 private readonly nearFactor: number = 0.6; private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; + private pitch: number; deltaFovLevel: number = 0; thresholdLevelForNear: number = -1; - level: number = -1; //当前渲染等级 - pitch: number; + level: number = -1; //当前渲染等级 viewMatrix: Matrix; projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far projViewMatrix: Matrix; @@ -77,6 +77,14 @@ class Camera extends Object3D { ); } + getPitch(): number{ + return this.pitch; + } + + setPitch(pitch: number): void{ + + } + getLightDirection(): Vector { var dirVertice = this.matrix.getColumnZ(); var direction = new Vector(-dirVertice.x, -dirVertice.y, -dirVertice.z); diff --git a/src/world/Event.ts b/src/world/Event.ts index 1c7b9ec..250da89 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -172,11 +172,11 @@ const EventModule = { //上、下、左、右:38、40、37、39 if (keyNum == 38 || keyNum == 40) { if (keyNum == 38) { - if (camera.pitch <= MIN_PITCH) { + if (camera.getPitch() <= MIN_PITCH) { return; } } else if (keyNum == 40) { - if (camera.pitch >= 90) { + if (camera.getPitch() >= 90) { return; } DELTA_PITCH *= -1; @@ -195,7 +195,7 @@ const EventModule = { dirZ.setLength(legnth2Intersect); var pNew = Vector.verticePlusVector(pIntersect, dirZ); camera.look(pNew, pIntersect); - camera.pitch -= DELTA_PITCH; + camera.setPitch(camera.getPitch() - DELTA_PITCH); globe.refresh(); } else { alert("视线与地球无交点"); diff --git a/src/world/Globe.ts b/src/world/Globe.ts index ed1b827..39553b0 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -145,7 +145,7 @@ class Globe { projView: projView, threshold: 1 }; - options.threshold = Math.min(90 / this.camera.pitch, 1.5); + options.threshold = Math.min(90 / this.camera.getPitch(), 1.5); //最大级别的level所对应的可见TileGrids var lastLevelTileGrids = this.camera.getVisibleTilesByLevel(level, options); var levelsTileGrids: any[] = []; //level-2 @@ -166,7 +166,6 @@ class Globe { levelsTileGrids.splice(0, 1); } } - } export = Globe; \ No newline at end of file diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index ec6c06f..e906352 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -7,16 +7,16 @@ 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, //渲染的基准层级 - MAX_LEVEL: 14,//最大的渲染级别 - EARTH_RADIUS: radius, - MAX_PROJECTED_COORD: maxProjectedCoord, - proxy: "" + renderer: null, + globe: null, + idCounter: 0, //Object3D对象的唯一标识 + BASE_LEVEL: 6, //渲染的基准层级 + MAX_LEVEL: 14,//最大的渲染级别 + EARTH_RADIUS: radius, + MAX_PROJECTED_COORD: maxProjectedCoord, + proxy: "" }; export = Kernel; \ No newline at end of file From 3171c01252c77902072cdc2aea8aa68dd5ef4a91 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sun, 27 Nov 2016 14:36:55 +0800 Subject: [PATCH 056/109] keep some fields and method private,#14,#18 --- src/world/Camera.ts | 156 ++++++++++-------------------- src/world/Event.ts | 2 +- src/world/Globe.ts | 4 +- src/world/Object3D.ts | 10 +- src/world/Renderer.ts | 1 - src/world/graphics/MeshGraphic.ts | 2 +- src/world/graphics/Poi.ts | 2 +- 7 files changed, 67 insertions(+), 110 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 3d1293f..858f736 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -16,17 +16,16 @@ class Camera extends Object3D { private readonly nearFactor: number = 0.6; private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; private pitch: number; - deltaFovLevel: number = 0; - thresholdLevelForNear: number = -1; - level: number = -1; //当前渲染等级 - viewMatrix: Matrix; - projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far - projViewMatrix: Matrix; + private level: number = -1; //当前渲染等级 + private viewMatrix: Matrix;//视点矩阵,即Camera模型矩阵的逆矩阵 + private projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far + private projViewMatrix: Matrix;//获取投影矩阵与视点矩阵的乘积 + private animating: boolean = false; + 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; //this.near一旦初始化之后就不应该再修改 //this.far可以动态计算 @@ -78,6 +77,8 @@ class Camera extends Object3D { } getPitch(): number{ + var lightDirection = this.getLightDirection(); + return this.pitch; } @@ -98,11 +99,8 @@ class Camera extends Object3D { return length2EarthSurface; } - //获取投影矩阵与视点矩阵的乘积 - getProjViewMatrix(): Matrix { - var viewMatrix = this.getViewMatrix(); - var projViewMatrix = this.projMatrix.multiplyMatrix(viewMatrix); - return projViewMatrix; + getProjViewMatrixForDraw(): Matrix{ + return this.projViewMatrix; } private _setFov(fov: number): void { @@ -179,15 +177,12 @@ class Camera extends Object3D { private _updatePositionAndFov(): number { //是否满足near值,和fov没有关系,和position有关 //但是改变position的话,fov也要相应变动以满足对应的缩放效果 - - // var safeThresholdLevel = this._getSafeThresholdLevelForNear(); - // if(this.getLevel() > safeThresholdLevel){ - - // } const currentLevel = this.getLevel(); - var safeLevel = this._getSafeThresholdLevelForNear(); + //_rawUpdatePositionByLevel()方法会修改this.matrix + //_setFov()方法会修改this.projMatrix + if(currentLevel > safeLevel){ //摄像机距离地球太近,导致不满足视景体的near值, //我们需要将摄像机的位置拉远,以满足near值 @@ -203,46 +198,6 @@ class Camera extends Object3D { this._setFov(this.initFov); } - // var distance2EarthSurface = this.getDistance2EarthSurface(); - // var thresholdNear = this.near * this.nearFactor; - // if (distance2EarthSurface <= thresholdNear) { - // //摄像机距离地球太近,导致不满足视景体的near值 - // //我们需要将摄像机的位置拉远,以满足near值 - - // //比如currentLevel为11,safeLevel为10 - // deltaLevel = currentLevel - safeLevel; - // //此处的deltaLevel应该为正数 - // if (deltaLevel !== 0) { - // newLevel = safeLevel; - // this._rawUpdatePositionByLevel(newLevel); - // //摄像机位置与地球表面距离变大之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 - // var newFov = this._calculateFovByDeltaLevel(this.initFov, deltaLevel); - // this._setFov(newFov); - // return this.fov; - // } - // } else { - // //现在满足near值 - - // if (this.fov < this.initFov * 0.95 && currentLevel <= safeLevel) { - // //如果当前fov的值比初始的initFov值小,说明当前的fov被缩放过(0.95用于精度问题,防止出现29.999999与30进行对比的情况) - // //比如currentLevel为11,this.fov为15,this.initFov为10,deltaLevel应该为负值 - // deltaLevel = this._calculateDeltaLevelByFov(this.fov, this.initFov); - // newLevel = currentLevel + deltaLevel; - // //上面的newLevel是float类型,向上取整,不要向下取整 - // newLevel = Math.ceil(newLevel); - // //deltaLevel应该为负整数 - // deltaLevel = newLevel - currentLevel; - // //比如currentLevel为11,newLevel为10,deltaLevel为-1 - // this._rawUpdatePositionByLevel(newLevel); - // var newFov = this._calculateFovByDeltaLevel(this.fov, deltaLevel); - // this._setFov(newFov); - // return this.fov; - // } else if (this.fov > this.initFov * 1.05) { - // //不应该出现当前fov值比初始initFov的大的情况 - // throw `_updatePositionAndFov() Invalid fov: ${this.fov}`; - // } - //} - return -1; } @@ -281,7 +236,6 @@ class Camera extends Object3D { var halfRadianNewFov = Math.atan(tanNew); var radianNewFov = halfRadianNewFov * 2; var newFov = MathUtils.radianToDegree(radianNewFov); - this.deltaFovLevel += deltaLevel; return newFov; } @@ -340,7 +294,13 @@ class Camera extends Object3D { } animateToLevel(level: number): void { - var newCamera = this._animateToLevel(level); + if (!(Utils.isNonNegativeInteger(level))) { + throw "invalid level:" + level; + } + var newCamera = this._clone(); + //don't call setLevel method because it will update CURRENT_LEVEL + newCamera._rawUpdatePositionByLevel(level); + this._animateToCamera(newCamera, () => { this.level = level; }); @@ -378,16 +338,6 @@ class Camera extends Object3D { requestAnimationFrame(callback); } - private _animateToLevel(level: number): Camera { - if (!(Utils.isNonNegativeInteger(level))) { - throw "invalid level:" + level; - } - var camera = this._clone(); - //don't call setLevel method because it will update CURRENT_LEVEL - camera._rawUpdatePositionByLevel(level); - return camera; - } - private _clone(): Camera { var camera: Camera = new Camera(); (Object).assign(camera, this.toJson()); @@ -426,19 +376,19 @@ class Camera extends Object3D { this._updateFar(); } - lookAt(targetPnt: Vertice, upDirection?: Vector): void { + private _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(); - } + private convertVerticeFromWorldToNDC(verticeInWorld: Vertice): Vertice { + // if (!(projViewMatrix instanceof Matrix)) { + // projViewMatrix = this.getProjViewMatrix(); + // } var columnWorld = [verticeInWorld.x, verticeInWorld.y, verticeInWorld.z, 1]; - var columnProject = projViewMatrix.multiplyColumn(columnWorld); + var columnProject = this.projViewMatrix.multiplyColumn(columnWorld); var w = columnProject[3]; var columnNDC: number[] = []; columnNDC[0] = columnProject[0] / w; @@ -450,7 +400,7 @@ class Camera extends Object3D { } //点变换: NDC->World - convertVerticeFromNdcToWorld(verticeInNDC: Vertice): Vertice { + 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); //带引号的“视坐标” @@ -468,7 +418,7 @@ class Camera extends Object3D { } //点变换: Camera->World - convertVerticeFromCameraToWorld(verticeInCamera: Vertice, /*optional*/ viewMatrix?: Matrix): Vertice { + private convertVerticeFromCameraToWorld(verticeInCamera: Vertice, /*optional*/ viewMatrix?: Matrix): Vertice { if (!(viewMatrix instanceof Matrix)) { viewMatrix = this.getViewMatrix(); } @@ -481,7 +431,7 @@ class Camera extends Object3D { } //向量变换: Camera->World - convertVectorFromCameraToWorld(vectorInCamera: Vector, /*optional*/ viewMatrix?: Matrix): Vector { + private convertVectorFromCameraToWorld(vectorInCamera: Vector, /*optional*/ viewMatrix?: Matrix): Vector { if (!(vectorInCamera instanceof Vector)) { throw "invalid vectorInCamera: not Vector"; } @@ -565,7 +515,7 @@ class Camera extends Object3D { } //得到摄像机的XOZ平面的方程 - getPlanXOZ(): Plan { + private getPlanXOZ(): Plan { var position = this.getPosition(); var direction = this.getLightDirection(); var plan = MathUtils.getCrossPlaneByLine(position, direction); @@ -574,7 +524,7 @@ class Camera extends Object3D { //判断世界坐标系中的点是否在Canvas中可见 //options:projView、verticeInNDC - isWorldVerticeVisibleInCanvas(verticeInWorld: Vertice, options?: any): boolean { + private isWorldVerticeVisibleInCanvas(verticeInWorld: Vertice, options?: any): boolean { if (!(verticeInWorld instanceof Vertice)) { throw "invalid verticeInWorld: not Vertice"; } @@ -590,10 +540,10 @@ class Camera extends Object3D { 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); + // if (!(options.projView instanceof Matrix)) { + // options.projView = this.getProjViewMatrix(); + // } + 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; @@ -604,7 +554,7 @@ class Camera extends Object3D { //判断地球表面的某个经纬度在Canvas中是否应该可见 //options:projView、verticeInNDC - isGeoVisibleInCanvas(lon: number, lat: number, options?: any): boolean { + private isGeoVisibleInCanvas(lon: number, lat: number, options?: any): boolean { var verticeInWorld = MathUtils.geographicToCartesianCoord(lon, lat); var result = this.isWorldVerticeVisibleInCanvas(verticeInWorld, options); return result; @@ -624,9 +574,9 @@ class Camera extends Object3D { } var result: TileGrid[] = []; options = options || {}; - if (!(options.projView instanceof Matrix)) { - options.projView = this.getProjViewMatrix(); - } + // if (!(options.projView instanceof Matrix)) { + // options.projView = this.getProjViewMatrix(); + // } //向左、向右、向上、向下最大的循环次数 var LOOP_LIMIT = Math.min(10, Math.pow(2, level) - 1); @@ -691,7 +641,7 @@ class Camera extends Object3D { return result; } - var verticalCenterInfo = this._getVerticalVisibleCenterInfo(options); + var verticalCenterInfo = this._getVerticalVisibleCenterInfo(); var centerGrid = TileGrid.getTileGridByGeo(verticalCenterInfo.lon, verticalCenterInfo.lat, level); var handleRowThis = handleRow.bind(this); @@ -735,7 +685,7 @@ class Camera extends Object3D { } //options:projView - getTileVisibleInfo(level: number, row: number, column: number, options?: any): any { + private getTileVisibleInfo(level: number, row: number, column: number, options?: any): any { if (!(level >= 0)) { throw "invalid level"; } @@ -783,9 +733,9 @@ class Camera extends Object3D { height: null, area: null }; - if (!(options.projView instanceof Matrix)) { - options.projView = this.getProjViewMatrix(); - } + // 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; @@ -796,7 +746,7 @@ class Camera extends Object3D { 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.verticeInNDC = this.convertVerticeFromWorldToNDC(result.lb.verticeInWorld); result.lb.visible = this.isWorldVerticeVisibleInCanvas(result.lb.verticeInWorld, { verticeInNDC: result.lb.verticeInNDC, projView: options.projView, @@ -810,7 +760,7 @@ class Camera extends Object3D { 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.verticeInNDC = this.convertVerticeFromWorldToNDC(result.lt.verticeInWorld); result.lt.visible = this.isWorldVerticeVisibleInCanvas(result.lt.verticeInWorld, { verticeInNDC: result.lt.verticeInNDC, projView: options.projView, @@ -824,7 +774,7 @@ class Camera extends Object3D { 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.verticeInNDC = this.convertVerticeFromWorldToNDC(result.rt.verticeInWorld); result.rt.visible = this.isWorldVerticeVisibleInCanvas(result.rt.verticeInWorld, { verticeInNDC: result.rt.verticeInNDC, projView: options.projView, @@ -838,7 +788,7 @@ class Camera extends Object3D { 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.verticeInNDC = this.convertVerticeFromWorldToNDC(result.rb.verticeInWorld); result.rb.visible = this.isWorldVerticeVisibleInCanvas(result.rb.verticeInWorld, { verticeInNDC: result.rb.verticeInNDC, projView: options.projView, @@ -869,11 +819,11 @@ class Camera extends Object3D { } //地球一直是关于纵轴中心对称的,获取垂直方向上中心点信息 - private _getVerticalVisibleCenterInfo(options?: any): any { - options = options || {}; - if (!options.projView) { - options.projView = this.getProjViewMatrix(); - } + private _getVerticalVisibleCenterInfo(): any { + // options = options || {}; + // if (!options.projView) { + // options.projView = this.getProjViewMatrix(); + // } var result = { ndcY: null, pIntersect: null, diff --git a/src/world/Event.ts b/src/world/Event.ts index 250da89..19b0cbe 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -187,7 +187,7 @@ const EventModule = { var pIntersect = pickResult[0]; var pCamera = camera.getPosition(); var legnth2Intersect = MathUtils.getLengthFromVerticeToVertice(pCamera, pIntersect); - var mat = camera.matrix.clone(); + var mat = camera.cloneMatrix(); mat.setColumnTrans(pIntersect.x, pIntersect.y, pIntersect.z); var DELTA_RADIAN = MathUtils.degreeToRadian(DELTA_PITCH); mat.localRotateX(DELTA_RADIAN); diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 39553b0..e83aad8 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -140,9 +140,9 @@ class Globe { this.camera.update(); var level = this.getLevel() + 3; this.tiledLayer.updateSubLayerCount(level); - var projView = this.camera.getProjViewMatrix(); + //var projView = this.camera.getProjViewMatrix(); var options = { - projView: projView, + //projView: projView, threshold: 1 }; options.threshold = Math.min(90 / this.camera.getPitch(), 1.5); diff --git a/src/world/Object3D.ts b/src/world/Object3D.ts index 9f64aa8..4be75e1 100644 --- a/src/world/Object3D.ts +++ b/src/world/Object3D.ts @@ -5,12 +5,20 @@ import Vertice = require('./math/Vertice'); import Vector = require('./math/Vector'); class Object3D { - matrix: Matrix; + protected matrix: Matrix; constructor() { this.matrix = new Matrix(); } + getMatrix(): Matrix{ + return this.matrix; + } + + cloneMatrix(): Matrix{ + return this.matrix.clone(); + } + //需要子类重写 getPosition(): Vertice { var position = this.matrix.getPosition(); diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 378d7c2..8105eb4 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -63,7 +63,6 @@ class Renderer { Kernel.gl.enable(Kernel.gl.DEPTH_TEST); Kernel.gl.depthFunc(Kernel.gl.LEQUAL); Kernel.gl.depthMask(true); - camera.viewMatrix = null; //update viewMatrix, projMatrix and projViewMatrix camera.update(); scene.draw(camera); diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index 0005ac1..c05353e 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -80,7 +80,7 @@ class MeshGraphic extends Graphic { gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 0, 0); //uPMVMatrix - var pmvMatrix = camera.projViewMatrix.multiplyMatrix(this.geometry.matrix); + var pmvMatrix = camera.getProjViewMatrixForDraw().multiplyMatrix(this.geometry.getMatrix()); var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index 50c7f20..c3d7b96 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -54,7 +54,7 @@ class Poi extends Graphic { gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 0, 0); //uPMVMatrix - var pmvMatrix = camera.projViewMatrix; + var pmvMatrix = camera.getProjViewMatrixForDraw(); var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.elements); From ea179d4360ecec974b208096358bba202e4ed5ed Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sun, 27 Nov 2016 15:14:17 +0800 Subject: [PATCH 057/109] optimize Camera: remove some uncessary local variables for method,#14 --- src/world/Camera.ts | 116 +++++++++++++------------------------------- 1 file changed, 35 insertions(+), 81 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 858f736..1c7046b 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -20,6 +20,7 @@ class Camera extends Object3D { private viewMatrix: Matrix;//视点矩阵,即Camera模型矩阵的逆矩阵 private projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far private projViewMatrix: Matrix;//获取投影矩阵与视点矩阵的乘积 + private projViewMatrixForDraw: Matrix;//实际传递给shader的矩阵是projViewMatrixForDraw,而不是projViewMatrix private animating: boolean = false; Enum: any = { @@ -76,6 +77,26 @@ class Camera extends Object3D { ); } + //更新各种矩阵,保守起见,可以在每帧绘制之前调用 + //理论上只在用户交互的时候调用就可以 + update(): void { + this.viewMatrix = null; + //视点矩阵是camera的模型矩阵的逆矩阵 + //this.viewMatrix = this.matrix.getInverseMatrix(); + + //通过修改position和fov以更新matrix和projMatrix + this._updatePositionAndFov(); + + //在_updatePositionAndFov()方法调用之后再计算viewMatrix + this.viewMatrix = this.matrix.getInverseMatrix(); + + //最后更新far + this._updateFar(); + + //update projViewMatrix + this.projViewMatrix = this.projMatrix.multiplyMatrix(this.viewMatrix); + } + getPitch(): number{ var lightDirection = this.getLightDirection(); @@ -126,27 +147,6 @@ class Camera extends Object3D { this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); } - getViewMatrix(): Matrix { - //视点矩阵是camera的模型矩阵的逆矩阵 - return this.matrix.getInverseMatrix(); - } - - update(): void { - this.viewMatrix = null; - - //通过修改position和fov以更新matrix和projMatrix - this._updatePositionAndFov(); - - //在_updatePositionAndFov()方法调用之后再计算viewMatrix - this.viewMatrix = this.getViewMatrix(); - - //最后更新far - this._updateFar(); - - //update projViewMatrix - this.projViewMatrix = this.projMatrix.multiplyMatrix(this.viewMatrix); - } - //计算从第几级level开始不满足视景体的near值 //比如第10级满足near,第11级不满足near,那么返回10 private _getSafeThresholdLevelForNear() { @@ -165,14 +165,6 @@ class Camera extends Object3D { return this.baseTheoryDistanceFromCamera2EarthSurface / Math.pow(2, level); } - private _setVirtualPosition(virtualPosisition: Vertice) { - - } - - private _getVirtualPosition(): Vertice { - return null; - } - //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov private _updatePositionAndFov(): number { //是否满足near值,和fov没有关系,和position有关 @@ -201,7 +193,6 @@ class Camera extends Object3D { return -1; } - //fov从oldFov变成了newFov,计算相当于缩放了几级level //比如从10级缩放到了第11级,fov从30变成了15,即oldFov为30,newFov为15,deltaLevel为1 //通过Math.log2()计算出结果,所以返回的是小数,可能是正数也可能是负数 @@ -384,9 +375,6 @@ class Camera extends Object3D { //点变换: World->NDC private convertVerticeFromWorldToNDC(verticeInWorld: Vertice): Vertice { - // if (!(projViewMatrix instanceof Matrix)) { - // projViewMatrix = this.getProjViewMatrix(); - // } var columnWorld = [verticeInWorld.x, verticeInWorld.y, verticeInWorld.z, 1]; var columnProject = this.projViewMatrix.multiplyColumn(columnWorld); var w = columnProject[3]; @@ -409,38 +397,25 @@ class Camera extends Object3D { 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 columnWorld = this.matrix.multiplyColumn(columnCamera); //单击点的世界坐标 var verticeInWorld = new Vertice(columnWorld[0], columnWorld[1], columnWorld[2]); return verticeInWorld; } //点变换: Camera->World - private convertVerticeFromCameraToWorld(verticeInCamera: Vertice, /*optional*/ viewMatrix?: Matrix): Vertice { - if (!(viewMatrix instanceof Matrix)) { - viewMatrix = this.getViewMatrix(); - } + private convertVerticeFromCameraToWorld(verticeInCamera: Vertice): Vertice { var verticeInCameraCopy = verticeInCamera.clone(); - var inverseMatrix = viewMatrix.getInverseMatrix(); var column = [verticeInCameraCopy.x, verticeInCameraCopy.y, verticeInCameraCopy.z, 1]; - var column2 = inverseMatrix.multiplyColumn(column); + var column2 = this.matrix.multiplyColumn(column); var verticeInWorld = new Vertice(column2[0], column2[1], column2[2]); return verticeInWorld; } //向量变换: Camera->World - private convertVectorFromCameraToWorld(vectorInCamera: Vector, /*optional*/ viewMatrix?: Matrix): Vector { - if (!(vectorInCamera instanceof Vector)) { - throw "invalid vectorInCamera: not Vector"; - } - if (!(viewMatrix instanceof Matrix)) { - viewMatrix = this.getViewMatrix(); - } + private convertVectorFromCameraToWorld(vectorInCamera: Vector): Vector { var vectorInCameraCopy = vectorInCamera.clone(); var verticeInCamera = vectorInCameraCopy.getVertice(); - var verticeInWorld = this.convertVerticeFromCameraToWorld(verticeInCamera, viewMatrix); + var verticeInWorld = this.convertVerticeFromCameraToWorld(verticeInCamera); var originInWorld = this.getPosition(); var vectorInWorld = Vector.verticeMinusVertice(verticeInWorld, originInWorld); vectorInWorld.normalize(); @@ -523,12 +498,8 @@ class Camera extends Object3D { } //判断世界坐标系中的点是否在Canvas中可见 - //options:projView、verticeInNDC - private isWorldVerticeVisibleInCanvas(verticeInWorld: Vertice, options?: any): boolean { - if (!(verticeInWorld instanceof Vertice)) { - throw "invalid verticeInWorld: not Vertice"; - } - options = options || {}; + //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); @@ -540,9 +511,6 @@ class Camera extends Object3D { 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); } var result = options.verticeInNDC.x >= -1 && options.verticeInNDC.x <= 1 && options.verticeInNDC.y >= -threshold && options.verticeInNDC.y <= 1; @@ -553,7 +521,7 @@ class Camera extends Object3D { } //判断地球表面的某个经纬度在Canvas中是否应该可见 - //options:projView、verticeInNDC + //options: verticeInNDC private isGeoVisibleInCanvas(lon: number, lat: number, options?: any): boolean { var verticeInWorld = MathUtils.geographicToCartesianCoord(lon, lat); var result = this.isWorldVerticeVisibleInCanvas(verticeInWorld, options); @@ -567,16 +535,12 @@ class Camera extends Object3D { * 3.形成的NDC四边形是顺时针方向 */ //获取level层级下的可见切片 - //options:projView - getVisibleTilesByLevel(level: number, options?: any): TileGrid[] { + //options: + 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); @@ -684,8 +648,8 @@ class Camera extends Object3D { return result; } - //options:projView - private getTileVisibleInfo(level: number, row: number, column: number, options?: any): any { + //options: threshold + private getTileVisibleInfo(level: number, row: number, column: number, options: any = {}): any { if (!(level >= 0)) { throw "invalid level"; } @@ -695,7 +659,7 @@ class Camera extends Object3D { if (!(column >= 0)) { throw "invalid column"; } - options = options || {}; + var threshold = typeof options.threshold == "number" ? Math.abs(options.threshold) : 1; var result: any = { lb: { @@ -733,9 +697,7 @@ class Camera extends Object3D { 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; @@ -749,7 +711,6 @@ class Camera extends Object3D { result.lb.verticeInNDC = this.convertVerticeFromWorldToNDC(result.lb.verticeInWorld); result.lb.visible = this.isWorldVerticeVisibleInCanvas(result.lb.verticeInWorld, { verticeInNDC: result.lb.verticeInNDC, - projView: options.projView, threshold: threshold }); if (result.lb.visible) { @@ -763,7 +724,6 @@ class Camera extends Object3D { result.lt.verticeInNDC = this.convertVerticeFromWorldToNDC(result.lt.verticeInWorld); result.lt.visible = this.isWorldVerticeVisibleInCanvas(result.lt.verticeInWorld, { verticeInNDC: result.lt.verticeInNDC, - projView: options.projView, threshold: threshold }); if (result.lt.visible) { @@ -777,7 +737,6 @@ class Camera extends Object3D { result.rt.verticeInNDC = this.convertVerticeFromWorldToNDC(result.rt.verticeInWorld); result.rt.visible = this.isWorldVerticeVisibleInCanvas(result.rt.verticeInWorld, { verticeInNDC: result.rt.verticeInNDC, - projView: options.projView, threshold: threshold }); if (result.rt.visible) { @@ -791,7 +750,6 @@ class Camera extends Object3D { result.rb.verticeInNDC = this.convertVerticeFromWorldToNDC(result.rb.verticeInWorld); result.rb.visible = this.isWorldVerticeVisibleInCanvas(result.rb.verticeInWorld, { verticeInNDC: result.rb.verticeInNDC, - projView: options.projView, threshold: threshold }); if (result.rb.visible) { @@ -820,10 +778,6 @@ class Camera extends Object3D { //地球一直是关于纵轴中心对称的,获取垂直方向上中心点信息 private _getVerticalVisibleCenterInfo(): any { - // options = options || {}; - // if (!options.projView) { - // options.projView = this.getProjViewMatrix(); - // } var result = { ndcY: null, pIntersect: null, From d8d7f6085899701718e3d7ec298fd67b53f4225a Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sun, 27 Nov 2016 15:23:50 +0800 Subject: [PATCH 058/109] rename methods of Camera --- src/world/Camera.ts | 134 ++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 1c7046b..22b1a47 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -373,54 +373,7 @@ class Camera extends Object3D { this.look(position, targetPntCopy, upDirection); } - //点变换: 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; - } //根据canvasX和canvasY获取拾取向量 getPickDirectionByCanvas(canvasX: number, canvasY: number): Vector { @@ -441,7 +394,7 @@ class Camera extends Object3D { //根据ndcX和ndcY获取拾取向量 getPickDirectionByNDC(ndcX: number, ndcY: number): Vector { var verticeInNDC = new Vertice(ndcX, ndcY, 0.499); - var verticeInWorld = this.convertVerticeFromNdcToWorld(verticeInNDC); + var verticeInWorld = this._convertVerticeFromNdcToWorld(verticeInNDC); var cameraPositon = this.getPosition(); //摄像机的世界坐标 var pickDirection = Vector.verticeMinusVertice(verticeInWorld, cameraPositon); pickDirection.normalize(); @@ -490,16 +443,65 @@ class Camera extends Object3D { } //得到摄像机的XOZ平面的方程 - private getPlanXOZ(): Plan { + 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 { + //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); @@ -511,7 +513,7 @@ class Camera extends Object3D { var length2Pick = MathUtils.getLengthFromVerticeToVertice(cameraP, pickVertice); if (length2Vertice < length2Pick + 5) { if (!(options.verticeInNDC instanceof Vertice)) { - options.verticeInNDC = this.convertVerticeFromWorldToNDC(verticeInWorld); + 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; @@ -522,9 +524,9 @@ class Camera extends Object3D { //判断地球表面的某个经纬度在Canvas中是否应该可见 //options: verticeInNDC - private isGeoVisibleInCanvas(lon: number, lat: number, options?: any): boolean { + private _isGeoVisibleInCanvas(lon: number, lat: number, options?: any): boolean { var verticeInWorld = MathUtils.geographicToCartesianCoord(lon, lat); - var result = this.isWorldVerticeVisibleInCanvas(verticeInWorld, options); + var result = this._isWorldVerticeVisibleInCanvas(verticeInWorld, options); return result; } @@ -561,7 +563,7 @@ class Camera extends Object3D { 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 visibleInfo = this._getTileVisibleInfo(grid.level, grid.row, grid.column, options); var isRowCenterVisible = checkVisible(visibleInfo); if (isRowCenterVisible) { (grid as any).visibleInfo = visibleInfo; @@ -575,7 +577,7 @@ class Camera extends Object3D { leftLoopTime++; grid = TileGrid.getTileGridByBrother(level, centerRow, leftColumn, MathUtils.LEFT, mathOptions); leftColumn = grid.column; - visibleInfo = this.getTileVisibleInfo(grid.level, grid.row, grid.column, options); + visibleInfo = this._getTileVisibleInfo(grid.level, grid.row, grid.column, options); visible = checkVisible(visibleInfo); if (visible) { (grid).visibleInfo = visibleInfo; @@ -592,7 +594,7 @@ class Camera extends Object3D { rightLoopTime++; grid = TileGrid.getTileGridByBrother(level, centerRow, rightColumn, MathUtils.RIGHT, mathOptions); rightColumn = grid.column; - visibleInfo = this.getTileVisibleInfo(grid.level, grid.row, grid.column, options); + visibleInfo = this._getTileVisibleInfo(grid.level, grid.row, grid.column, options); visible = checkVisible(visibleInfo); if (visible) { (grid).visibleInfo = visibleInfo; @@ -649,7 +651,7 @@ class Camera extends Object3D { } //options: threshold - private getTileVisibleInfo(level: number, row: number, column: number, options: any = {}): any { + private _getTileVisibleInfo(level: number, row: number, column: number, options: any = {}): any { if (!(level >= 0)) { throw "invalid level"; } @@ -708,8 +710,8 @@ class Camera extends Object3D { 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, { + result.lb.verticeInNDC = this._convertVerticeFromWorldToNDC(result.lb.verticeInWorld); + result.lb.visible = this._isWorldVerticeVisibleInCanvas(result.lb.verticeInWorld, { verticeInNDC: result.lb.verticeInNDC, threshold: threshold }); @@ -721,8 +723,8 @@ class Camera extends Object3D { 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, { + result.lt.verticeInNDC = this._convertVerticeFromWorldToNDC(result.lt.verticeInWorld); + result.lt.visible = this._isWorldVerticeVisibleInCanvas(result.lt.verticeInWorld, { verticeInNDC: result.lt.verticeInNDC, threshold: threshold }); @@ -734,8 +736,8 @@ class Camera extends Object3D { 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, { + result.rt.verticeInNDC = this._convertVerticeFromWorldToNDC(result.rt.verticeInWorld); + result.rt.visible = this._isWorldVerticeVisibleInCanvas(result.rt.verticeInWorld, { verticeInNDC: result.rt.verticeInNDC, threshold: threshold }); @@ -747,8 +749,8 @@ class Camera extends Object3D { 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, { + result.rb.verticeInNDC = this._convertVerticeFromWorldToNDC(result.rb.verticeInWorld); + result.rb.visible = this._isWorldVerticeVisibleInCanvas(result.rb.verticeInWorld, { verticeInNDC: result.rb.verticeInNDC, threshold: threshold }); From 98166252facab6f9e04d0da59287a6d86d1f4a52 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sun, 27 Nov 2016 15:51:59 +0800 Subject: [PATCH 059/109] rename methods of Camera,#14 --- src/world/Camera.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 22b1a47..50c4a67 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -317,7 +317,7 @@ class Camera extends Object3D { } var a = timestap - start; if (a >= span) { - (Object).assign(this, newCamera.toJson()); + (Object).assign(this, newCamera._toJson()); this.animating = false; cb(); } else { @@ -331,11 +331,11 @@ class Camera extends Object3D { private _clone(): Camera { var camera: Camera = new Camera(); - (Object).assign(camera, this.toJson()); + (Object).assign(camera, this._toJson()); return camera; } - toJson(): any { + private _toJson(): any { return { pitch: this.pitch, near: this.near, @@ -373,12 +373,10 @@ class Camera extends Object3D { this.look(position, targetPntCopy, upDirection); } - - //根据canvasX和canvasY获取拾取向量 - getPickDirectionByCanvas(canvasX: number, canvasY: number): Vector { + private _getPickDirectionByCanvas(canvasX: number, canvasY: number): Vector { var ndcXY = MathUtils.convertPointFromCanvasToNDC(canvasX, canvasY); - var pickDirection = this.getPickDirectionByNDC(ndcXY[0], ndcXY[1]); + var pickDirection = this._getPickDirectionByNDC(ndcXY[0], ndcXY[1]); return pickDirection; } @@ -392,7 +390,7 @@ class Camera extends Object3D { } //根据ndcX和ndcY获取拾取向量 - getPickDirectionByNDC(ndcX: number, ndcY: number): Vector { + private _getPickDirectionByNDC(ndcX: number, ndcY: number): Vector { var verticeInNDC = new Vertice(ndcX, ndcY, 0.499); var verticeInWorld = this._convertVerticeFromNdcToWorld(verticeInNDC); var cameraPositon = this.getPosition(); //摄像机的世界坐标 @@ -401,7 +399,7 @@ class Camera extends Object3D { return pickDirection; } - //获取直线与地球的交点,该方法与World.Math.getLineIntersectPointWithEarth功能基本一样,只不过该方法对相交点进行了远近排序 + //获取直线与地球的交点,该方法与MathUtils.getLineIntersectPointWithEarth功能基本一样,只不过该方法对相交点进行了远近排序 getPickCartesianCoordInEarthByLine(line: Line): Vertice[] { var result: Vertice[] = []; //pickVertice是笛卡尔空间直角坐标系中的坐标 @@ -427,15 +425,15 @@ class Camera extends Object3D { //计算拾取射线与地球的交点,以笛卡尔空间直角坐标系坐标数组的形式返回 getPickCartesianCoordInEarthByCanvas(canvasX: number, canvasY: number): Vertice[] { - var pickDirection = this.getPickDirectionByCanvas(canvasX, canvasY); + 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); + 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); @@ -797,7 +795,7 @@ class Camera extends Object3D { var ndcY: number; //从上往下找topNdcY for (ndcY = 1.0; ndcY >= -1.0; ndcY -= delta) { - pickResults = this.getPickCartesianCoordInEarthByNDC(0, ndcY); + pickResults = this._getPickCartesianCoordInEarthByNDC(0, ndcY); if (pickResults.length > 0) { topNdcY = ndcY; break; @@ -806,7 +804,7 @@ class Camera extends Object3D { //从下往上找 for (ndcY = -1.0; ndcY <= 1.0; ndcY += delta) { - pickResults = this.getPickCartesianCoordInEarthByNDC(0, ndcY); + pickResults = this._getPickCartesianCoordInEarthByNDC(0, ndcY); if (pickResults.length > 0) { bottomNdcY = ndcY; break; @@ -814,7 +812,7 @@ class Camera extends Object3D { } result.ndcY = (topNdcY + bottomNdcY) / 2; } - pickResults = this.getPickCartesianCoordInEarthByNDC(0, result.ndcY); + pickResults = this._getPickCartesianCoordInEarthByNDC(0, result.ndcY); result.pIntersect = pickResults[0]; var lonlat = MathUtils.cartesianCoordToGeographic(result.pIntersect); result.lon = lonlat[0]; From b8de76cd0826c6fef7d621700b5347da09d142f8 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sun, 27 Nov 2016 16:59:17 +0800 Subject: [PATCH 060/109] update,#14 --- src/world/Camera.ts | 131 ++++++++++++++++++++++++-------------------- src/world/Event.ts | 10 +++- 2 files changed, 80 insertions(+), 61 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 50c4a67..f637821 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -80,71 +80,38 @@ class Camera extends Object3D { //更新各种矩阵,保守起见,可以在每帧绘制之前调用 //理论上只在用户交互的时候调用就可以 update(): void { - this.viewMatrix = null; - //视点矩阵是camera的模型矩阵的逆矩阵 - //this.viewMatrix = this.matrix.getInverseMatrix(); - - //通过修改position和fov以更新matrix和projMatrix - this._updatePositionAndFov(); + this._normalUpdate(); + //this._updateProjViewMatrixForDraw(); + } - //在_updatePositionAndFov()方法调用之后再计算viewMatrix + _normalUpdate(){ + //视点矩阵是camera的模型矩阵的逆矩阵 this.viewMatrix = this.matrix.getInverseMatrix(); - //最后更新far + //通过修改far值更新projMatrix this._updateFar(); - //update projViewMatrix + //更新projViewMatrix this.projViewMatrix = this.projMatrix.multiplyMatrix(this.viewMatrix); } - getPitch(): number{ - var lightDirection = this.getLightDirection(); - - return this.pitch; - } - - setPitch(pitch: number): void{ - - } - - getLightDirection(): Vector { - var dirVertice = this.matrix.getColumnZ(); - 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; - } + _updateProjViewMatrixForDraw(){ + var matrix = this.matrix.clone(); + var viewMatrix = this.viewMatrix.clone(); + var projMatrix = this.projMatrix.clone(); + var projViewMatrix = this.projViewMatrix.clone(); - getProjViewMatrixForDraw(): Matrix{ - return this.projViewMatrix; - } + //通过修改position和fov以更新matrix和projMatrix + this._updatePositionAndFov(); - private _setFov(fov: number): void { - if (!(fov > 0)) { - throw "invalid fov:" + fov; - } - this._setPerspectiveMatrix(fov, this.aspect, this.near, this.far); - } + //在_updatePositionAndFov()方法调用之后再计算viewMatrix + this.viewMatrix = this.matrix.getInverseMatrix(); - setAspect(aspect: number): void { - if (!(aspect > 0)) { - throw "invalid aspect:" + aspect; - } - this._setPerspectiveMatrix(this.fov, aspect, this.near, this.far); - } + //最后更新far + this._updateFar(); - private _updateFar(): void { - //重新计算far,保持far在满足正常需求情况下的最小值 - //far值:视点与地球切面的距离 - var length2EarthOrigin = Vector.fromVertice(this.getPosition()).getLength(); - var far = Math.sqrt(length2EarthOrigin * length2EarthOrigin - Kernel.EARTH_RADIUS * Kernel.EARTH_RADIUS); - far *= 1.05; - this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); + //update projViewMatrix + this.projViewMatrixForDraw = projMatrix.multiplyMatrix(viewMatrix); } //计算从第几级level开始不满足视景体的near值 @@ -166,14 +133,14 @@ class Camera extends Object3D { } //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov - private _updatePositionAndFov(): number { + private _updatePositionAndFov() { //是否满足near值,和fov没有关系,和position有关 //但是改变position的话,fov也要相应变动以满足对应的缩放效果 const currentLevel = this.getLevel(); var safeLevel = this._getSafeThresholdLevelForNear(); - //_rawUpdatePositionByLevel()方法会修改this.matrix - //_setFov()方法会修改this.projMatrix + //_rawUpdatePositionByLevel()方法会修改matrix + //_setFov()方法会修改projMatrix if(currentLevel > safeLevel){ //摄像机距离地球太近,导致不满足视景体的near值, @@ -189,8 +156,6 @@ class Camera extends Object3D { this._rawUpdatePositionByLevel(currentLevel); this._setFov(this.initFov); } - - return -1; } //fov从oldFov变成了newFov,计算相当于缩放了几级level @@ -280,6 +245,56 @@ class Camera extends Object3D { return true; } + getPitch(): number{ + var lightDirection = this.getLightDirection(); + + return this.pitch; + } + + setPitch(pitch: number): void{ + + } + + getLightDirection(): Vector { + var dirVertice = this.matrix.getColumnZ(); + 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; + } + + getProjViewMatrixForDraw(): Matrix{ + return this.projViewMatrix; + } + + 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 { + //重新计算far,保持far在满足正常需求情况下的最小值 + //far值:视点与地球切面的距离 + var length2EarthOrigin = Vector.fromVertice(this.getPosition()).getLength(); + var far = Math.sqrt(length2EarthOrigin * length2EarthOrigin - Kernel.EARTH_RADIUS * Kernel.EARTH_RADIUS); + far *= 1.05; + this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); + } + isAnimating(): boolean { return this.animating; } diff --git a/src/world/Event.ts b/src/world/Event.ts index 19b0cbe..74c49c5 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -159,6 +159,10 @@ const EventModule = { } }, + /** + * 通过向上和向下的键盘按键调整Camera视线方向的倾斜角度pitch + * 初始pitch值为0 + */ onKeyDown(event: KeyboardEvent) { var globe = Kernel.globe; if (!globe) { @@ -186,19 +190,19 @@ const EventModule = { if (pickResult.length > 0) { var pIntersect = pickResult[0]; var pCamera = camera.getPosition(); - var legnth2Intersect = MathUtils.getLengthFromVerticeToVertice(pCamera, pIntersect); + var distance2Intersect = MathUtils.getLengthFromVerticeToVertice(pCamera, pIntersect); var mat = camera.cloneMatrix(); 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); + dirZ.setLength(distance2Intersect); var pNew = Vector.verticePlusVector(pIntersect, dirZ); camera.look(pNew, pIntersect); camera.setPitch(camera.getPitch() - DELTA_PITCH); globe.refresh(); } else { - alert("视线与地球无交点"); + console.log("视线与地球无交点"); } } } From 1fc49578069280efdffcd36076627c2618b53019 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sun, 27 Nov 2016 17:18:29 +0800 Subject: [PATCH 061/109] add algorithm for pitch,#14 --- src/world/Event.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/world/Event.ts b/src/world/Event.ts index 74c49c5..54877c7 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -162,6 +162,15 @@ const EventModule = { /** * 通过向上和向下的键盘按键调整Camera视线方向的倾斜角度pitch * 初始pitch值为0 + * 当在A时刻要将pitch值从0变成一个非0值时,我们需要记录A时刻Camera的模型矩阵A.matrix和当时的级别A.level + * 然后经过多次修改pitch值以及多次缩放后到达B时刻,B时刻的pitch值为B.pitch,层级为B.level + * 即便摄像机视线没有指向真实地球的中心了,我们也会假象一个虚拟地球,让我们的经过pitch倾斜后的实现指向虚拟地球的中心以便套用公式计算缩放距离 + * 我们这样确定在B时刻摄像机的matrix: + * 1. 我们计算的起点是A时刻Camera的初始状态(A.matrix和A.level) + * 2. 将A.matrix.localRotateX(B.pitch),这样就将摄像机旋转了,即确定了B时刻摄像机模型坐标系坐标轴的方向 + * 3. 我们还需要确定B时刻摄像机的位置,按照标准公式计算在A.level层级下,Camera距离地球表面的距离应该是A.distance2Surface + * 相应的,按照标准公式计算在B.level层级下,Camera距离地球表面的距离应该是B.distance2Surface + * 那么我们将A.matrix.getPosition()沿着B.matrix.getLightDirection()视线往前移动(B.distance2Surface - A.distance2Surface)的距离即可 */ onKeyDown(event: KeyboardEvent) { var globe = Kernel.globe; From 97d058f94f7228c182b71704b82522ad95dc753f Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Sun, 27 Nov 2016 18:36:03 +0800 Subject: [PATCH 062/109] update camera.setPitch() and setLevel()method, but not work as expected,#14 --- src/world/Camera.ts | 75 +++++++++++++++++++++++++++++++++++---------- src/world/Event.ts | 38 ++++------------------- 2 files changed, 64 insertions(+), 49 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index f637821..b0acd9e 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -15,12 +15,19 @@ class Camera extends Object3D { private readonly animationDuration: number = 600;//层级变化的动画周期是600毫秒 private readonly nearFactor: number = 0.6; private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; - private pitch: number; + private readonly maxPitch = 54; + private level: number = -1; //当前渲染等级 + + private pitch: number;//Camera视线的倾斜角度,初始值为0,表示视线经过球心,单位为角度。正值表示抬头,赋值表示低头。 + private pitchStartMatrix: Matrix;//从0开始倾斜视线时刻的模型矩阵,如果pitch=0,那么pitchStartMatrix为null值。 + private pitchStartLevel: number = -1;//从0开始倾斜视线时刻的level,如果pitch=0,那么pitchStartLevel为负值。 + private viewMatrix: Matrix;//视点矩阵,即Camera模型矩阵的逆矩阵 private projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far private projViewMatrix: Matrix;//获取投影矩阵与视点矩阵的乘积 private projViewMatrixForDraw: Matrix;//实际传递给shader的矩阵是projViewMatrixForDraw,而不是projViewMatrix + private animating: boolean = false; Enum: any = { @@ -35,7 +42,7 @@ class Camera extends Object3D { constructor(private fov = 45, private aspect = 1, private near = 1, private far = 100) { super(); this.initFov = this.fov; - this.pitch = 90; + this.pitch = 0; this.projMatrix = new Matrix(); this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); } @@ -228,31 +235,65 @@ class Camera extends Object3D { var newPosition = vector.getVertice(); this.look(newPosition, origin); } else { - var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 - var vector = this.getLightDirection().getOpposite(); - vector.setLength(length); - var newPosition = vector.getVertice(); - this.setPosition(newPosition.x, newPosition.y, newPosition.z); - - // var distance2SurfaceNow = this._getTheoryDistanceFromCamera2EarthSurface(this.getLevel()); - // var distance2SurfaceNew = this._getTheoryDistanceFromCamera2EarthSurface(level); - // var deltaDistance = distance2SurfaceNow - distance2SurfaceNew; - // var dir = this.getLightDirection(); - // dir.setLength(deltaDistance); - // var pNew = Vector.verticePlusVector(pOld, dir); - // this.setPosition(pNew.x, pNew.y, pNew.z); + // if(this.pitch === 0){ + // var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 + // var vector = this.getLightDirection().getOpposite().setLength(length); + // var newPosition = vector.getVertice(); + // this.setPosition(newPosition.x, newPosition.y, newPosition.z); + // }else{ + // } + var distance2SurfaceOld = this._getTheoryDistanceFromCamera2EarthSurface(this.getLevel()); + var distance2SurfaceNew = this._getTheoryDistanceFromCamera2EarthSurface(level); + var deltaDistance = distance2SurfaceOld - distance2SurfaceNew; + var dir = this.getLightDirection().setLength(deltaDistance); + var pNew = Vector.verticePlusVector(pOld, dir); + this.setPosition(pNew.x, pNew.y, pNew.z); } return true; } getPitch(): number{ - var lightDirection = this.getLightDirection(); - return this.pitch; } setPitch(pitch: number): void{ + if(this.pitch === pitch || pitch >= this.maxPitch){ + return; + } + + if(pitch < 0){ + pitch = 0; + } + + if(this.pitch === 0){ + //没有倾斜=>倾斜 + this.pitchStartMatrix = this.matrix.clone(); + this.pitchStartLevel = this.level; + } + + //处理旋转角度 + this.pitch = pitch; + var radian = MathUtils.degreeToRadian(this.pitch); + var newMatrix = this.pitchStartMatrix.clone(); + newMatrix.localRotateX(radian); + + //处理level + var distance2SurfaceLevel1 = this._getTheoryDistanceFromCamera2EarthSurface(this.pitchStartLevel); + var distance2SurfaceLevel2 = this._getTheoryDistanceFromCamera2EarthSurface(this.level); + var deltaDistance = distance2SurfaceLevel2 - distance2SurfaceLevel1; + var lightDirection = newMatrix.getColumnZ().getOpposite().setLength(deltaDistance); + var newPosition = Vector.verticePlusVector(newMatrix.getColumnTrans(), lightDirection); + newMatrix.setColumnTrans(newPosition.x, newPosition.y, newPosition.z); + + //如果当前的pitch为0,重置pitchStartMatrix和pitchStartLevel + if(this.pitch === 0){ + this.pitchStartMatrix = null; + this.pitchStartLevel = -1; + } + //更新this.matrix + this.matrix = newMatrix; + Kernel.globe.refresh(); } getLightDirection(): Vector { diff --git a/src/world/Event.ts b/src/world/Event.ts index 54877c7..29fc3bb 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -178,44 +178,18 @@ const EventModule = { 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.getPitch() <= MIN_PITCH) { - return; - } - } else if (keyNum == 40) { - if (camera.getPitch() >= 90) { - return; - } - DELTA_PITCH *= -1; - } - - var pickResult = camera.getDirectionIntersectPointWithEarth(); - if (pickResult.length > 0) { - var pIntersect = pickResult[0]; - var pCamera = camera.getPosition(); - var distance2Intersect = MathUtils.getLengthFromVerticeToVertice(pCamera, pIntersect); - var mat = camera.cloneMatrix(); - 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(distance2Intersect); - var pNew = Vector.verticePlusVector(pIntersect, dirZ); - camera.look(pNew, pIntersect); - camera.setPitch(camera.getPitch() - DELTA_PITCH); - globe.refresh(); - } else { - console.log("视线与地球无交点"); - } + if(keyNum === 38){ + //向上键 + camera.setPitch(camera.getPitch() + DELTA_PITCH); + }else if(keyNum === 40){ + //向下键 + camera.setPitch(camera.getPitch() - DELTA_PITCH); } } - }; export = EventModule; \ No newline at end of file From 451248a69ddc8afb1b182ca4908aabc4152aa834 Mon Sep 17 00:00:00 2001 From: iSpring Date: Mon, 28 Nov 2016 00:40:43 +0800 Subject: [PATCH 063/109] update setDeltaPitch,getPitch and setLevel,#14 --- src/world/Camera.ts | 140 ++++++++++++++++++++++++++++---------------- src/world/Event.ts | 4 +- 2 files changed, 91 insertions(+), 53 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index b0acd9e..4941e49 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -19,9 +19,9 @@ class Camera extends Object3D { private level: number = -1; //当前渲染等级 - private pitch: number;//Camera视线的倾斜角度,初始值为0,表示视线经过球心,单位为角度。正值表示抬头,赋值表示低头。 - private pitchStartMatrix: Matrix;//从0开始倾斜视线时刻的模型矩阵,如果pitch=0,那么pitchStartMatrix为null值。 - private pitchStartLevel: number = -1;//从0开始倾斜视线时刻的level,如果pitch=0,那么pitchStartLevel为负值。 + private isPitchZero: boolean = true;//表示当前Camera视线有没有发生倾斜 + //private pitchStartMatrix: Matrix;//从0开始倾斜视线时刻的模型矩阵,如果pitch=0,那么pitchStartMatrix为null值。 + //private pitchStartLevel: number = -1;//从0开始倾斜视线时刻的level,如果pitch=0,那么pitchStartLevel为负值。 private viewMatrix: Matrix;//视点矩阵,即Camera模型矩阵的逆矩阵 private projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far @@ -42,7 +42,6 @@ class Camera extends Object3D { constructor(private fov = 45, private aspect = 1, private near = 1, private far = 100) { super(); this.initFov = this.fov; - this.pitch = 0; this.projMatrix = new Matrix(); this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, this.far); } @@ -91,7 +90,7 @@ class Camera extends Object3D { //this._updateProjViewMatrixForDraw(); } - _normalUpdate(){ + _normalUpdate() { //视点矩阵是camera的模型矩阵的逆矩阵 this.viewMatrix = this.matrix.getInverseMatrix(); @@ -102,7 +101,7 @@ class Camera extends Object3D { this.projViewMatrix = this.projMatrix.multiplyMatrix(this.viewMatrix); } - _updateProjViewMatrixForDraw(){ + _updateProjViewMatrixForDraw() { var matrix = this.matrix.clone(); var viewMatrix = this.viewMatrix.clone(); var projMatrix = this.projMatrix.clone(); @@ -149,7 +148,7 @@ class Camera extends Object3D { //_rawUpdatePositionByLevel()方法会修改matrix //_setFov()方法会修改projMatrix - if(currentLevel > safeLevel){ + if (currentLevel > safeLevel) { //摄像机距离地球太近,导致不满足视景体的near值, //我们需要将摄像机的位置拉远,以满足near值 this._rawUpdatePositionByLevel(safeLevel); @@ -159,7 +158,7 @@ class Camera extends Object3D { //deltaLevel应该为正正数,计算出的newFov应该比this.initFov要小 var newFov = this._calculateFovByDeltaLevel(this.initFov, deltaLevel); this._setFov(newFov); - }else{ + } else { this._rawUpdatePositionByLevel(currentLevel); this._setFov(this.initFov); } @@ -242,60 +241,100 @@ class Camera extends Object3D { // this.setPosition(newPosition.x, newPosition.y, newPosition.z); // }else{ // } - var distance2SurfaceOld = this._getTheoryDistanceFromCamera2EarthSurface(this.getLevel()); - var distance2SurfaceNew = this._getTheoryDistanceFromCamera2EarthSurface(level); - var deltaDistance = distance2SurfaceOld - distance2SurfaceNew; - var dir = this.getLightDirection().setLength(deltaDistance); - var pNew = Vector.verticePlusVector(pOld, dir); - this.setPosition(pNew.x, pNew.y, pNew.z); + //无论Camera视线倾斜多少,永远保持视线与地球的交叉点到摄像机的距离满足公式 + // var distance2Intersect = this._getTheoryDistanceFromCamera2EarthSurface(level); + // var distance2SurfaceOld = this._getTheoryDistanceFromCamera2EarthSurface(this.getLevel()); + // var distance2SurfaceNew = this._getTheoryDistanceFromCamera2EarthSurface(level); + // var deltaDistance = distance2SurfaceOld - distance2SurfaceNew; + // var dir = this.getLightDirection().setLength(deltaDistance); + // var pNew = Vector.verticePlusVector(pOld, dir); + // this.setPosition(pNew.x, pNew.y, pNew.z); + var matrix = this.matrix.clone(); + var hasIntersect = this._calculateNewCameraPositionByLevel(matrix, level); + if (hasIntersect) { + this.matrix = matrix; + } else { + throw "_rawUpdatePositionByLevel: light direction doesn't have intersects with earth"; + } } return true; } - getPitch(): number{ - return this.pitch; + _calculateNewCameraPositionByLevel(matrix: Matrix, level: number): boolean { + //matrix表示已经调整了pitch后的摄像机模型矩阵,要根据level计算matrix的position + var direction = matrix.getColumnZ().getOpposite(); + var currentCameraPosition = matrix.getPosition(); + var line = new Line(currentCameraPosition, direction); + var intersects = this.getPickCartesianCoordInEarthByLine(line); + //我们要保证经过这次旋转之后,视线会与地球有交点 + if (intersects.length < 2) { + return false; + } + var intersect = intersects[0]; + var camera2Intersect = MathUtils.getLengthFromVerticeToVertice(currentCameraPosition, intersect); + var theoryDistance2Interscet = this._getTheoryDistanceFromCamera2EarthSurface(this.level); + var deltaVectorFromIntersect2NewPosition = matrix.getColumnZ().normalize().setLength(theoryDistance2Interscet); + var newCameraPosition = Vector.verticePlusVector(intersect, deltaVectorFromIntersect2NewPosition); + matrix.setColumnTrans(newCameraPosition.x, newCameraPosition.y, newCameraPosition.z); + return true; } - setPitch(pitch: number): void{ - if(this.pitch === pitch || pitch >= this.maxPitch){ + setDeltaPitch(deltaPitch: number) { + var currentPitch = this.getPitch(); + var newPitch = currentPitch + deltaPitch; + if (newPitch > this.maxPitch) { return; } - - if(pitch < 0){ - pitch = 0; - } - - if(this.pitch === 0){ - //没有倾斜=>倾斜 - this.pitchStartMatrix = this.matrix.clone(); - this.pitchStartLevel = this.level; + if (newPitch < 0) { + newPitch = 0; } - - //处理旋转角度 - this.pitch = pitch; - var radian = MathUtils.degreeToRadian(this.pitch); - var newMatrix = this.pitchStartMatrix.clone(); - newMatrix.localRotateX(radian); - - //处理level - var distance2SurfaceLevel1 = this._getTheoryDistanceFromCamera2EarthSurface(this.pitchStartLevel); - var distance2SurfaceLevel2 = this._getTheoryDistanceFromCamera2EarthSurface(this.level); - var deltaDistance = distance2SurfaceLevel2 - distance2SurfaceLevel1; - var lightDirection = newMatrix.getColumnZ().getOpposite().setLength(deltaDistance); - var newPosition = Vector.verticePlusVector(newMatrix.getColumnTrans(), lightDirection); - newMatrix.setColumnTrans(newPosition.x, newPosition.y, newPosition.z); - - //如果当前的pitch为0,重置pitchStartMatrix和pitchStartLevel - if(this.pitch === 0){ - this.pitchStartMatrix = null; - this.pitchStartLevel = -1; + //我们要保证经过这次旋转之后,视线会与地球有交点 + // var distance2Origin = Vector.fromVertice(this.getPosition()).getLength(); + // var sinv = Kernel.EARTH_RADIUS / distance2Origin; + // var maxPitch = MathUtils.radianToDegree(Math.asin(sinv)); + // if(newPitch > maxPitch){ + // return; + // } + + //计算最终的deltaPitch + deltaPitch = newPitch - currentPitch; + var deltaRadian = MathUtils.degreeToRadian(deltaPitch); + //先不对this.matrix进行更新,对其拷贝进行更新 + var matrix = this.matrix.clone(); + matrix.localRotateX(deltaRadian); + var hasIntersect = this._calculateNewCameraPositionByLevel(matrix, this.level); + if (!hasIntersect) { + //我们要保证经过这次旋转之后,视线会与地球有交点 + return; } - //更新this.matrix - this.matrix = newMatrix; + //刷新 + this.isPitchZero = newPitch === 0; + this.matrix = matrix; Kernel.globe.refresh(); } + //pitch表示Camera视线的倾斜角度,初始值为0,表示视线经过球心,单位为角度,范围是[0, this.maxPitch] + getPitch(): number { + //计算夹角 + var lightDirection = this.getLightDirection(); + var vectorFromCmeraToEarthOrigin = Vector.fromVertice(this.getPosition()).getOpposite().normalize(); + var cosθ = lightDirection.dot(vectorFromCmeraToEarthOrigin); + var radian = Math.acos(cosθ); + + //计算夹角的正负 + var crossVector = vectorFromCmeraToEarthOrigin.cross(lightDirection).normalize(); + var zAxisDirection = this.matrix.getColumnZ().normalize(); + if (crossVector.dot(zAxisDirection) > 0) { + //正值 + radian = Math.abs(radian); + } else { + //负值 + radian = - Math.abs(radian); + } + return MathUtils.radianToDegree(radian); + } + getLightDirection(): Vector { var dirVertice = this.matrix.getColumnZ(); var direction = new Vector(-dirVertice.x, -dirVertice.y, -dirVertice.z); @@ -309,7 +348,7 @@ class Camera extends Object3D { return length2EarthSurface; } - getProjViewMatrixForDraw(): Matrix{ + getProjViewMatrixForDraw(): Matrix { return this.projViewMatrix; } @@ -393,7 +432,6 @@ class Camera extends Object3D { private _toJson(): any { return { - pitch: this.pitch, near: this.near, far: this.far, fov: this.fov, @@ -841,7 +879,7 @@ class Camera extends Object3D { lat: null }; var pickResults: Vertice[]; - if (this.pitch == 90) { + if (this.isPitchZero) { result.ndcY = 0; } else { var count = 10; diff --git a/src/world/Event.ts b/src/world/Event.ts index 29fc3bb..8d7b36c 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -184,10 +184,10 @@ const EventModule = { //上、下、左、右:38、40、37、39 if(keyNum === 38){ //向上键 - camera.setPitch(camera.getPitch() + DELTA_PITCH); + camera.setDeltaPitch(DELTA_PITCH); }else if(keyNum === 40){ //向下键 - camera.setPitch(camera.getPitch() - DELTA_PITCH); + camera.setDeltaPitch(DELTA_PITCH); } } }; From a085f090120ed03da88d183db471ff6ba57859c6 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Mon, 28 Nov 2016 12:54:17 +0800 Subject: [PATCH 064/109] fix bug of camera._calculateNewCameraPositionByLevel() method,#14 --- src/world/Camera.ts | 2 +- src/world/Event.ts | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 4941e49..29d09ad 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -272,7 +272,7 @@ class Camera extends Object3D { } var intersect = intersects[0]; var camera2Intersect = MathUtils.getLengthFromVerticeToVertice(currentCameraPosition, intersect); - var theoryDistance2Interscet = this._getTheoryDistanceFromCamera2EarthSurface(this.level); + var theoryDistance2Interscet = this._getTheoryDistanceFromCamera2EarthSurface(level); var deltaVectorFromIntersect2NewPosition = matrix.getColumnZ().normalize().setLength(theoryDistance2Interscet); var newCameraPosition = Vector.verticePlusVector(intersect, deltaVectorFromIntersect2NewPosition); matrix.setColumnTrans(newCameraPosition.x, newCameraPosition.y, newCameraPosition.z); diff --git a/src/world/Event.ts b/src/world/Event.ts index 8d7b36c..f7e47ad 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -162,15 +162,6 @@ const EventModule = { /** * 通过向上和向下的键盘按键调整Camera视线方向的倾斜角度pitch * 初始pitch值为0 - * 当在A时刻要将pitch值从0变成一个非0值时,我们需要记录A时刻Camera的模型矩阵A.matrix和当时的级别A.level - * 然后经过多次修改pitch值以及多次缩放后到达B时刻,B时刻的pitch值为B.pitch,层级为B.level - * 即便摄像机视线没有指向真实地球的中心了,我们也会假象一个虚拟地球,让我们的经过pitch倾斜后的实现指向虚拟地球的中心以便套用公式计算缩放距离 - * 我们这样确定在B时刻摄像机的matrix: - * 1. 我们计算的起点是A时刻Camera的初始状态(A.matrix和A.level) - * 2. 将A.matrix.localRotateX(B.pitch),这样就将摄像机旋转了,即确定了B时刻摄像机模型坐标系坐标轴的方向 - * 3. 我们还需要确定B时刻摄像机的位置,按照标准公式计算在A.level层级下,Camera距离地球表面的距离应该是A.distance2Surface - * 相应的,按照标准公式计算在B.level层级下,Camera距离地球表面的距离应该是B.distance2Surface - * 那么我们将A.matrix.getPosition()沿着B.matrix.getLightDirection()视线往前移动(B.distance2Surface - A.distance2Surface)的距离即可 */ onKeyDown(event: KeyboardEvent) { var globe = Kernel.globe; From 55f4999177297a6336ac26ae3f8a12a8bc982ba6 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Mon, 28 Nov 2016 17:54:46 +0800 Subject: [PATCH 065/109] update refactor,#14 --- src/world/Camera.ts | 50 ++++++++++++++++----------------- src/world/Event.ts | 2 +- src/world/VertexBufferObject.ts | 12 ++++---- src/world/math/Math.ts | 14 +++++++++ 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 29d09ad..3204c47 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -226,7 +226,7 @@ class Camera extends Object3D { var globe = Kernel.globe; var pOld = this.getPosition(); if (pOld.x === 0 && pOld.y === 0 && pOld.z === 0) { - //初始设置camera + //init camera var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 var origin = new Vertice(0, 0, 0); var vector = this.getLightDirection().getOpposite(); @@ -234,27 +234,24 @@ class Camera extends Object3D { var newPosition = vector.getVertice(); this.look(newPosition, origin); } else { - // if(this.pitch === 0){ - // var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; //level等级下摄像机应该到球心的距离 - // var vector = this.getLightDirection().getOpposite().setLength(length); - // var newPosition = vector.getVertice(); - // this.setPosition(newPosition.x, newPosition.y, newPosition.z); - // }else{ - // } - //无论Camera视线倾斜多少,永远保持视线与地球的交叉点到摄像机的距离满足公式 - // var distance2Intersect = this._getTheoryDistanceFromCamera2EarthSurface(level); - // var distance2SurfaceOld = this._getTheoryDistanceFromCamera2EarthSurface(this.getLevel()); - // var distance2SurfaceNew = this._getTheoryDistanceFromCamera2EarthSurface(level); - // var deltaDistance = distance2SurfaceOld - distance2SurfaceNew; - // var dir = this.getLightDirection().setLength(deltaDistance); - // var pNew = Vector.verticePlusVector(pOld, dir); - // this.setPosition(pNew.x, pNew.y, pNew.z); - var matrix = this.matrix.clone(); - var hasIntersect = this._calculateNewCameraPositionByLevel(matrix, level); - if (hasIntersect) { - this.matrix = matrix; - } else { - throw "_rawUpdatePositionByLevel: light direction doesn't have intersects with earth"; + var currentPosition = this.getPosition(); + if(this.isPitchZero){ + var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; + var vector = this.getLightDirection().getOpposite().setLength(length); + var newPosition = vector.getVertice(); + this.setPosition(newPosition.x, newPosition.y, newPosition.z); + }else{ + var intersects = this.getDirectionIntersectPointWithEarth(); + if(intersects.length === 0){ + throw "no intersect"; + } + var intersect = intersects[0]; + var distance2Intersect = MathUtils.getLengthFromVerticeToVertice(currentPosition, intersect); + var deltaLevel = level - this.level; + var newDistance2Intersect = distance2Intersect / Math.pow(2, deltaLevel); + var deltaDistance = distance2Intersect - newDistance2Intersect; + var deltaVector = this.getLightDirection().setLength(deltaDistance); + var newPosition = Vector.verticePlusVector(currentPosition, deltaVector); } } return true; @@ -298,6 +295,9 @@ class Camera extends Object3D { //计算最终的deltaPitch deltaPitch = newPitch - currentPitch; + if(deltaPitch === 0){ + return; + } var deltaRadian = MathUtils.degreeToRadian(deltaPitch); //先不对this.matrix进行更新,对其拷贝进行更新 var matrix = this.matrix.clone(); @@ -320,12 +320,12 @@ class Camera extends Object3D { var lightDirection = this.getLightDirection(); var vectorFromCmeraToEarthOrigin = Vector.fromVertice(this.getPosition()).getOpposite().normalize(); var cosθ = lightDirection.dot(vectorFromCmeraToEarthOrigin); - var radian = Math.acos(cosθ); + var radian = MathUtils.acosSafely(cosθ); //计算夹角的正负 var crossVector = vectorFromCmeraToEarthOrigin.cross(lightDirection).normalize(); - var zAxisDirection = this.matrix.getColumnZ().normalize(); - if (crossVector.dot(zAxisDirection) > 0) { + var xAxisDirection = this.matrix.getColumnX().normalize(); + if (crossVector.dot(xAxisDirection) > 0) { //正值 radian = Math.abs(radian); } else { diff --git a/src/world/Event.ts b/src/world/Event.ts index f7e47ad..eaf3b49 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -178,7 +178,7 @@ const EventModule = { camera.setDeltaPitch(DELTA_PITCH); }else if(keyNum === 40){ //向下键 - camera.setDeltaPitch(DELTA_PITCH); + camera.setDeltaPitch(-DELTA_PITCH); } } }; diff --git a/src/world/VertexBufferObject.ts b/src/world/VertexBufferObject.ts index 49e9155..be2006b 100644 --- a/src/world/VertexBufferObject.ts +++ b/src/world/VertexBufferObject.ts @@ -22,15 +22,17 @@ class VertexBufferObject{ this.bind(); } - if(this.target === Kernel.gl.ARRAY_BUFFER){ - Kernel.gl.bufferData(Kernel.gl.ARRAY_BUFFER, new Float32Array(data), usage); - }else if(this.target === Kernel.gl.ELEMENT_ARRAY_BUFFER){ - Kernel.gl.bufferData(Kernel.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), usage); + 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(Kernel.gl.isBuffer(this.buffer)){ + if(this.buffer){ Kernel.gl.deleteBuffer(this.buffer); } this.buffer = null; diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index e72b5f5..34bed55 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -29,6 +29,20 @@ const MathUtils = { return ( value & ( value - 1 ) ) === 0 && value !== 0; }, + asinSafely(value: number){ + if(value > 1){ + value = 1; + } + return Math.asin(value); + }, + + acosSafely(value: number){ + if(value > 1){ + value = 1; + } + return Math.acos(value); + }, + /** * 将其他进制的数字转换为10进制 * @param numSys 要准换的进制 From c8e0abe23619ea342982c36516f214fe5742df1f Mon Sep 17 00:00:00 2001 From: iSpring Date: Mon, 28 Nov 2016 22:13:24 +0800 Subject: [PATCH 066/109] update getPitch() method of Camera,#14 --- src/world/Camera.ts | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 3204c47..e4ff990 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -19,9 +19,9 @@ class Camera extends Object3D { private level: number = -1; //当前渲染等级 - private isPitchZero: boolean = true;//表示当前Camera视线有没有发生倾斜 - //private pitchStartMatrix: Matrix;//从0开始倾斜视线时刻的模型矩阵,如果pitch=0,那么pitchStartMatrix为null值。 - //private pitchStartLevel: number = -1;//从0开始倾斜视线时刻的level,如果pitch=0,那么pitchStartLevel为负值。 + //旋转的时候,绕着视线与地球交点进行旋转 + //定义抬头时,旋转角为正值 + private isZeroPitch: boolean = true;//表示当前Camera视线有没有发生倾斜 private viewMatrix: Matrix;//视点矩阵,即Camera模型矩阵的逆矩阵 private projMatrix: Matrix;//当Matrix变化的时候,需要重新计算this.far @@ -235,7 +235,7 @@ class Camera extends Object3D { this.look(newPosition, origin); } else { var currentPosition = this.getPosition(); - if(this.isPitchZero){ + if(this.isZeroPitch){ var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; var vector = this.getLightDirection().getOpposite().setLength(length); var newPosition = vector.getVertice(); @@ -285,13 +285,6 @@ class Camera extends Object3D { if (newPitch < 0) { newPitch = 0; } - //我们要保证经过这次旋转之后,视线会与地球有交点 - // var distance2Origin = Vector.fromVertice(this.getPosition()).getLength(); - // var sinv = Kernel.EARTH_RADIUS / distance2Origin; - // var maxPitch = MathUtils.radianToDegree(Math.asin(sinv)); - // if(newPitch > maxPitch){ - // return; - // } //计算最终的deltaPitch deltaPitch = newPitch - currentPitch; @@ -309,29 +302,38 @@ class Camera extends Object3D { } //刷新 - this.isPitchZero = newPitch === 0; + this.isZeroPitch = newPitch === 0; this.matrix = matrix; Kernel.globe.refresh(); } //pitch表示Camera视线的倾斜角度,初始值为0,表示视线经过球心,单位为角度,范围是[0, this.maxPitch] getPitch(): number { + var intersects = this.getDirectionIntersectPointWithEarth(); + if(intersects.length === 0){ + throw "no intersects"; + } + var intersect = intersects[0]; + //计算夹角 - var lightDirection = this.getLightDirection(); - var vectorFromCmeraToEarthOrigin = Vector.fromVertice(this.getPosition()).getOpposite().normalize(); - var cosθ = lightDirection.dot(vectorFromCmeraToEarthOrigin); + 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 = vectorFromCmeraToEarthOrigin.cross(lightDirection).normalize(); - var xAxisDirection = this.matrix.getColumnX().normalize(); - if (crossVector.dot(xAxisDirection) > 0) { + var crossVector = vectorOrigin2Intersect.cross(vectorIntersect2Camera); + var xAxisDirection = this.matrix.getColumnX() + if(crossVector.dot(xAxisDirection)){ //正值 radian = Math.abs(radian); - } else { + }else{ //负值 radian = - Math.abs(radian); } + return MathUtils.radianToDegree(radian); } @@ -879,7 +881,7 @@ class Camera extends Object3D { lat: null }; var pickResults: Vertice[]; - if (this.isPitchZero) { + if (this.isZeroPitch) { result.ndcY = 0; } else { var count = 10; From f58b7c90457205c59c8f9b6d58ff13f65edf01a6 Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 12:15:22 +0800 Subject: [PATCH 067/109] update Camera,#14 --- src/world/Camera.ts | 182 ++++++++++++++++++++------------------------ src/world/Globe.ts | 2 - 2 files changed, 83 insertions(+), 101 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index e4ff990..983479a 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -44,6 +44,7 @@ class Camera extends Object3D { this.initFov = this.fov; 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 { @@ -103,15 +104,15 @@ class Camera extends Object3D { _updateProjViewMatrixForDraw() { var matrix = this.matrix.clone(); - var viewMatrix = this.viewMatrix.clone(); + // var viewMatrix = this.viewMatrix.clone(); var projMatrix = this.projMatrix.clone(); - var projViewMatrix = this.projViewMatrix.clone(); + // var projViewMatrix = this.projViewMatrix.clone(); //通过修改position和fov以更新matrix和projMatrix - this._updatePositionAndFov(); + this._updatePositionAndFov(matrix, projMatrix); //在_updatePositionAndFov()方法调用之后再计算viewMatrix - this.viewMatrix = this.matrix.getInverseMatrix(); + var viewMatrix = matrix.getInverseMatrix(); //最后更新far this._updateFar(); @@ -120,26 +121,8 @@ class Camera extends Object3D { this.projViewMatrixForDraw = projMatrix.multiplyMatrix(viewMatrix); } - //计算从第几级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); - } - - /** - * 根据层级计算出摄像机应该放置到距离地球表面多远的位置 - * @param level - * @return {*} - */ - private _getTheoryDistanceFromCamera2EarthSurface(level: number): number { - return this.baseTheoryDistanceFromCamera2EarthSurface / Math.pow(2, level); - } - //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov - private _updatePositionAndFov() { + private _updatePositionAndFov(cameraMatrix: Matrix, projMatrix: Matrix) { //是否满足near值,和fov没有关系,和position有关 //但是改变position的话,fov也要相应变动以满足对应的缩放效果 const currentLevel = this.getLevel(); @@ -151,7 +134,7 @@ class Camera extends Object3D { if (currentLevel > safeLevel) { //摄像机距离地球太近,导致不满足视景体的near值, //我们需要将摄像机的位置拉远,以满足near值 - this._rawUpdatePositionByLevel(safeLevel); + this._updatePositionByLevel(safeLevel, cameraMatrix); //比如safeLevel是10,而currentLevel是11,则deltaLevel为1 var deltaLevel = currentLevel - safeLevel; //摄像机位置与地球表面距离变大之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 @@ -159,11 +142,29 @@ class Camera extends Object3D { var newFov = this._calculateFovByDeltaLevel(this.initFov, deltaLevel); this._setFov(newFov); } else { - this._rawUpdatePositionByLevel(currentLevel); + this._updatePositionByLevel(currentLevel, cameraMatrix); this._setFov(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); + } + + /** + * 根据层级计算出摄像机应该放置到距离地球表面多远的位置 + * @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()计算出结果,所以返回的是小数,可能是正数也可能是负数 @@ -206,74 +207,42 @@ class Camera extends Object3D { } setLevel(level: number): void { - var isLevelChanged = this._rawUpdatePositionByLevel(level); - if (isLevelChanged) { - //不要在this._setLevel()方法中更新this.level,因为这会影响animateToLevel()方法 - this.level = level; - Kernel.globe.refresh(); - } - } - - //设置观察到的层级,不要在该方法中修改this.level的值 - private _rawUpdatePositionByLevel(level: number): boolean { if (!(Utils.isNonNegativeInteger(level))) { throw "invalid level:" + level; } level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 if (level === this.level) { - return false; - } - var globe = Kernel.globe; - var pOld = this.getPosition(); - if (pOld.x === 0 && pOld.y === 0 && pOld.z === 0) { - //init camera - var length = this._getTheoryDistanceFromCamera2EarthSurface(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 currentPosition = this.getPosition(); - if(this.isZeroPitch){ - var length = this._getTheoryDistanceFromCamera2EarthSurface(level) + Kernel.EARTH_RADIUS; - var vector = this.getLightDirection().getOpposite().setLength(length); - var newPosition = vector.getVertice(); - this.setPosition(newPosition.x, newPosition.y, newPosition.z); - }else{ - var intersects = this.getDirectionIntersectPointWithEarth(); - if(intersects.length === 0){ - throw "no intersect"; - } - var intersect = intersects[0]; - var distance2Intersect = MathUtils.getLengthFromVerticeToVertice(currentPosition, intersect); - var deltaLevel = level - this.level; - var newDistance2Intersect = distance2Intersect / Math.pow(2, deltaLevel); - var deltaDistance = distance2Intersect - newDistance2Intersect; - var deltaVector = this.getLightDirection().setLength(deltaDistance); - var newPosition = Vector.verticePlusVector(currentPosition, deltaVector); - } + return; } - return true; + var isLevelChanged = this._updatePositionByLevel(level, this.matrix); + //不要在this._setLevel()方法中更新this.level,因为这会影响animateToLevel()方法 + this.level = level; + Kernel.globe.refresh(); } - _calculateNewCameraPositionByLevel(matrix: Matrix, level: number): boolean { - //matrix表示已经调整了pitch后的摄像机模型矩阵,要根据level计算matrix的position - var direction = matrix.getColumnZ().getOpposite(); - var currentCameraPosition = matrix.getPosition(); - var line = new Line(currentCameraPosition, direction); - var intersects = this.getPickCartesianCoordInEarthByLine(line); - //我们要保证经过这次旋转之后,视线会与地球有交点 - if (intersects.length < 2) { - return false; + 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 camera2Intersect = MathUtils.getLengthFromVerticeToVertice(currentCameraPosition, intersect); + var intersect = intersects[0]; var theoryDistance2Interscet = this._getTheoryDistanceFromCamera2EarthSurface(level); - var deltaVectorFromIntersect2NewPosition = matrix.getColumnZ().normalize().setLength(theoryDistance2Interscet); - var newCameraPosition = Vector.verticePlusVector(intersect, deltaVectorFromIntersect2NewPosition); - matrix.setColumnTrans(newCameraPosition.x, newCameraPosition.y, newCameraPosition.z); - return true; + var vector = cameraMatrix.getColumnZ(); + vector.setLength(theoryDistance2Interscet); + var newCameraPosition = Vector.verticePlusVector(intersect, vector); + cameraMatrix.setColumnTrans(newCameraPosition.x, newCameraPosition.y, newCameraPosition.z); } setDeltaPitch(deltaPitch: number) { @@ -288,18 +257,25 @@ class Camera extends Object3D { //计算最终的deltaPitch deltaPitch = newPitch - currentPitch; - if(deltaPitch === 0){ + if (deltaPitch === 0) { return; } + + var intersects = this.getDirectionIntersectPointWithEarth(); + 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.setColumnTrans(intersect.x, intersect.y, intersect.z); + //旋转 matrix.localRotateX(deltaRadian); - var hasIntersect = this._calculateNewCameraPositionByLevel(matrix, this.level); - if (!hasIntersect) { - //我们要保证经过这次旋转之后,视线会与地球有交点 - return; - } + //更新matrix的position + this._updatePositionByLevel(this.level, matrix); //刷新 this.isZeroPitch = newPitch === 0; @@ -309,8 +285,11 @@ class Camera extends Object3D { //pitch表示Camera视线的倾斜角度,初始值为0,表示视线经过球心,单位为角度,范围是[0, this.maxPitch] getPitch(): number { + if (this.isZeroPitch) { + return 0; + } var intersects = this.getDirectionIntersectPointWithEarth(); - if(intersects.length === 0){ + if (intersects.length === 0) { throw "no intersects"; } var intersect = intersects[0]; @@ -326,10 +305,10 @@ class Camera extends Object3D { //计算夹角的正负 var crossVector = vectorOrigin2Intersect.cross(vectorIntersect2Camera); var xAxisDirection = this.matrix.getColumnX() - if(crossVector.dot(xAxisDirection)){ + if (crossVector.dot(xAxisDirection)) { //正值 radian = Math.abs(radian); - }else{ + } else { //负值 radian = - Math.abs(radian); } @@ -387,7 +366,7 @@ class Camera extends Object3D { } var newCamera = this._clone(); //don't call setLevel method because it will update CURRENT_LEVEL - newCamera._rawUpdatePositionByLevel(level); + // newCamera._updatePositionByLevel(level); this._animateToCamera(newCamera, () => { this.level = level; @@ -443,7 +422,7 @@ class Camera extends Object3D { }; } - look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { + 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(); @@ -466,7 +445,7 @@ class Camera extends Object3D { private _lookAt(targetPnt: Vertice, upDirection?: Vector): void { var targetPntCopy = targetPnt.clone(); var position = this.getPosition(); - this.look(position, targetPntCopy, upDirection); + this._look(position, targetPntCopy, upDirection); } //根据canvasX和canvasY获取拾取向量 @@ -476,15 +455,20 @@ class Camera extends Object3D { return pickDirection; } - //获取当前视线与地球的交点 - getDirectionIntersectPointWithEarth(): Vertice[] { - var dir = this.getLightDirection(); - var p = this.getPosition(); + private _getDirectionIntersectPointWithEarth(cameraMatrix: Matrix): Vertice[]{ + var dir = cameraMatrix.getColumnZ().getOpposite(); + var p = cameraMatrix.getPosition(); var line = new Line(p, dir); var result = this.getPickCartesianCoordInEarthByLine(line); return result; } + //获取当前视线与地球的交点 + getDirectionIntersectPointWithEarth(): Vertice[] { + var result = this._getDirectionIntersectPointWithEarth(this.matrix); + return result; + } + //根据ndcX和ndcY获取拾取向量 private _getPickDirectionByNDC(ndcX: number, ndcY: number): Vector { var verticeInNDC = new Vertice(ndcX, ndcY, 0.499); diff --git a/src/world/Globe.ts b/src/world/Globe.ts index e83aad8..5d73811 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -140,9 +140,7 @@ class Globe { this.camera.update(); var level = this.getLevel() + 3; this.tiledLayer.updateSubLayerCount(level); - //var projView = this.camera.getProjViewMatrix(); var options = { - //projView: projView, threshold: 1 }; options.threshold = Math.min(90 / this.camera.getPitch(), 1.5); From c1eea4b0a685f2e2f3b04a7a84449196b8a27183 Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 13:15:31 +0800 Subject: [PATCH 068/109] update projViewMatrixForDraw and use it to draw graphics, basically solve the depth issue,#14 --- src/world/Camera.ts | 125 ++++++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 58 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 983479a..f3bb2b9 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -15,7 +15,7 @@ class Camera extends Object3D { private readonly animationDuration: number = 600;//层级变化的动画周期是600毫秒 private readonly nearFactor: number = 0.6; private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; - private readonly maxPitch = 54; + private readonly maxPitch = 40; private level: number = -1; //当前渲染等级 @@ -52,31 +52,34 @@ class Camera extends Object3D { this._updateFar(); } - private _rawSetPerspectiveMatrix(fov: number = 45, aspect: number = 1, near: number = 1, far: number = 100): void { + 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 - this.fov = fov; - this.aspect = aspect; - this.near = near; - this.far = far; + 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 = this.fov * Math.PI / 180 / 2; + var halfFov = fov * Math.PI / 180 / 2; var f = 1 / Math.tan(halfFov); - var nf = 1 / (this.near - this.far); + var nf = 1 / (near - far); - mat[0] = f / this.aspect; + mat[0] = f / aspect; mat[5] = f; - mat[10] = (this.far + this.near) * nf; + mat[10] = (far + near) * nf; mat[11] = -1; - mat[14] = 2 * this.near * this.far * nf; + mat[14] = 2 * near * far * nf; mat[15] = 0; //by comparision with matrixProjection.exe and glMatrix, the 11th element is always -1 - this.projMatrix.setElements( + 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], @@ -84,11 +87,43 @@ class Camera extends Object3D { ); } + 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(): void { this._normalUpdate(); - //this._updateProjViewMatrixForDraw(); + this._updateProjViewMatrixForDraw(); + } + + getProjViewMatrixForDraw(): Matrix { + return this.projViewMatrixForDraw; } _normalUpdate() { @@ -103,34 +138,35 @@ class Camera extends Object3D { } _updateProjViewMatrixForDraw() { - var matrix = this.matrix.clone(); - // var viewMatrix = this.viewMatrix.clone(); - var projMatrix = this.projMatrix.clone(); - // var projViewMatrix = this.projViewMatrix.clone(); + var newMatrix = this.matrix.clone(); - //通过修改position和fov以更新matrix和projMatrix - this._updatePositionAndFov(matrix, projMatrix); + //通过修改position以更新matrix + var newFov = this._updatePositionAndFov(newMatrix); + var aspect = this.aspect; + var near = this.near; - //在_updatePositionAndFov()方法调用之后再计算viewMatrix - var viewMatrix = matrix.getInverseMatrix(); + //计算newFar + var newPosition = newMatrix.getPosition(); + var newFar = this._getMinimalFar(newPosition); - //最后更新far - this._updateFar(); + //根据newFov和newFar重新计算 + var newProjMatrix = new Matrix(); + this._rawSetPerspectiveMatrix(newFov, aspect, near, newFar, newProjMatrix); + + //在_updatePositionAndFov()方法调用之后再计算newViewMatrix + var newViewMatrix = newMatrix.getInverseMatrix(); - //update projViewMatrix - this.projViewMatrixForDraw = projMatrix.multiplyMatrix(viewMatrix); + //最后计算projViewMatrixForDraw + this.projViewMatrixForDraw = newProjMatrix.multiplyMatrix(newViewMatrix); } //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov - private _updatePositionAndFov(cameraMatrix: Matrix, projMatrix: Matrix) { + private _updatePositionAndFov(cameraMatrix: Matrix): number { //是否满足near值,和fov没有关系,和position有关 //但是改变position的话,fov也要相应变动以满足对应的缩放效果 const currentLevel = this.getLevel(); var safeLevel = this._getSafeThresholdLevelForNear(); - //_rawUpdatePositionByLevel()方法会修改matrix - //_setFov()方法会修改projMatrix - if (currentLevel > safeLevel) { //摄像机距离地球太近,导致不满足视景体的near值, //我们需要将摄像机的位置拉远,以满足near值 @@ -140,10 +176,10 @@ class Camera extends Object3D { //摄像机位置与地球表面距离变大之后,我们看到的地球变小,为此,我们需要把fov值变小,以抵消摄像机位置距离增大导致的变化 //deltaLevel应该为正正数,计算出的newFov应该比this.initFov要小 var newFov = this._calculateFovByDeltaLevel(this.initFov, deltaLevel); - this._setFov(newFov); + return newFov; } else { this._updatePositionByLevel(currentLevel, cameraMatrix); - this._setFov(this.initFov); + return this.initFov; } } @@ -329,33 +365,6 @@ class Camera extends Object3D { return length2EarthSurface; } - getProjViewMatrixForDraw(): Matrix { - return this.projViewMatrix; - } - - 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 { - //重新计算far,保持far在满足正常需求情况下的最小值 - //far值:视点与地球切面的距离 - var length2EarthOrigin = Vector.fromVertice(this.getPosition()).getLength(); - var far = Math.sqrt(length2EarthOrigin * length2EarthOrigin - Kernel.EARTH_RADIUS * Kernel.EARTH_RADIUS); - far *= 1.05; - this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); - } - isAnimating(): boolean { return this.animating; } From 97387e9d0cbb43bc1a07248aeee04a0fe5a6c005 Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 18:29:40 +0800 Subject: [PATCH 069/109] don't udpate far because it can zoom out the extent a little,#14 --- src/world/Camera.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index f3bb2b9..0f69bdf 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -102,8 +102,8 @@ class Camera extends Object3D { } private _updateFar(): void { - var far = this._getMinimalFar(this.matrix.getPosition()); - this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); + // var far = this._getMinimalFar(this.matrix.getPosition()); + // this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); } private _getMinimalFar(cameraPosition: Vertice): number{ @@ -147,7 +147,7 @@ class Camera extends Object3D { //计算newFar var newPosition = newMatrix.getPosition(); - var newFar = this._getMinimalFar(newPosition); + var newFar = this.far; //this._getMinimalFar(newPosition); //根据newFov和newFar重新计算 var newProjMatrix = new Matrix(); From fb104cb54ba9da76251ca9103262d9181826720c Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 19:22:11 +0800 Subject: [PATCH 070/109] make Camera.getPickCartesinanCoordInEarthByCanvas() work with no error by using projViewMatrixForDraw instead of projViewMatrix,#14 --- main.js | 6 ++-- src/world/Camera.ts | 78 +++++++++++++++++++++++++++++---------------- src/world/Event.ts | 2 +- src/world/Kernel.ts | 2 +- 4 files changed, 56 insertions(+), 32 deletions(-) diff --git a/main.js b/main.js index 62e9a21..075db09 100644 --- a/main.js +++ b/main.js @@ -1,10 +1,12 @@ window.onload = function() { - require(["world/Globe", "world/layers/BingTiledLayer", "world/layers/NokiaTiledLayer", "world/layers/OsmTiledLayer", + require(["world/Kernel", "world/Globe", "world/layers/BingTiledLayer", "world/layers/NokiaTiledLayer", "world/layers/OsmTiledLayer", "world/layers/SosoTiledLayer", "world/layers/TiandituTiledLayer", "world/layers/GoogleTiledLayer", "world/layers/PoiLayer"], - function(Globe, BingTiledLayer, NokiaTiledLayer, OsmTiledLayer, SosoTiledLayer, TiandituTiledLayer, GoogleTiledLayer, + function(Kernel, Globe, BingTiledLayer, NokiaTiledLayer, OsmTiledLayer, SosoTiledLayer, TiandituTiledLayer, GoogleTiledLayer, PoiLayer) { + window.Kernel = Kernel; + function startWebGL() { var canvas = document.getElementById("canvasId"); window.globe = new Globe(canvas); diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 0f69bdf..cb4bb85 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -26,6 +26,10 @@ class Camera extends Object3D { 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; @@ -138,26 +142,26 @@ class Camera extends Object3D { } _updateProjViewMatrixForDraw() { - var newMatrix = this.matrix.clone(); + this.matrixForDraw = this.matrix.clone(); //通过修改position以更新matrix - var newFov = this._updatePositionAndFov(newMatrix); + var newFov = this._updatePositionAndFov(this.matrixForDraw); var aspect = this.aspect; var near = this.near; //计算newFar - var newPosition = newMatrix.getPosition(); + var newPosition = this.matrixForDraw.getPosition(); var newFar = this.far; //this._getMinimalFar(newPosition); //根据newFov和newFar重新计算 - var newProjMatrix = new Matrix(); - this._rawSetPerspectiveMatrix(newFov, aspect, near, newFar, newProjMatrix); + this.projMatrixForDraw = new Matrix(); + this._rawSetPerspectiveMatrix(newFov, aspect, near, newFar, this.projMatrixForDraw); //在_updatePositionAndFov()方法调用之后再计算newViewMatrix - var newViewMatrix = newMatrix.getInverseMatrix(); + this.viewMatrixForDraw = this.matrixForDraw.getInverseMatrix(); //最后计算projViewMatrixForDraw - this.projViewMatrixForDraw = newProjMatrix.multiplyMatrix(newViewMatrix); + this.projViewMatrixForDraw = this.projMatrixForDraw.multiplyMatrix(this.viewMatrixForDraw); } //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov @@ -297,7 +301,7 @@ class Camera extends Object3D { return; } - var intersects = this.getDirectionIntersectPointWithEarth(); + var intersects = this._getDirectionIntersectPointWithEarth(this.matrix); if (intersects.length === 0) { throw "no intersects"; } @@ -324,7 +328,7 @@ class Camera extends Object3D { if (this.isZeroPitch) { return 0; } - var intersects = this.getDirectionIntersectPointWithEarth(); + var intersects = this._getDirectionIntersectPointWithEarth(this.matrix); if (intersects.length === 0) { throw "no intersects"; } @@ -352,6 +356,38 @@ class Camera extends Object3D { return MathUtils.radianToDegree(radian); } + //计算拾取射线与地球的交点,以笛卡尔空间直角坐标系坐标数组的形式返回 + //该方法需要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.getColumnZ(); var direction = new Vector(-dirVertice.x, -dirVertice.y, -dirVertice.z); @@ -464,17 +500,12 @@ class Camera extends Object3D { return pickDirection; } + //获取cameraMatrix视线与地球的交点 private _getDirectionIntersectPointWithEarth(cameraMatrix: Matrix): Vertice[]{ var dir = cameraMatrix.getColumnZ().getOpposite(); var p = cameraMatrix.getPosition(); var line = new Line(p, dir); - var result = this.getPickCartesianCoordInEarthByLine(line); - return result; - } - - //获取当前视线与地球的交点 - getDirectionIntersectPointWithEarth(): Vertice[] { - var result = this._getDirectionIntersectPointWithEarth(this.matrix); + var result = this._getPickCartesianCoordInEarthByLine(line); return result; } @@ -489,7 +520,7 @@ class Camera extends Object3D { } //获取直线与地球的交点,该方法与MathUtils.getLineIntersectPointWithEarth功能基本一样,只不过该方法对相交点进行了远近排序 - getPickCartesianCoordInEarthByLine(line: Line): Vertice[] { + private _getPickCartesianCoordInEarthByLine(line: Line): Vertice[] { var result: Vertice[] = []; //pickVertice是笛卡尔空间直角坐标系中的坐标 var pickVertices = MathUtils.getLineIntersectPointWithEarth(line); @@ -512,20 +543,11 @@ class Camera extends Object3D { 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; - } - 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); + var result = this._getPickCartesianCoordInEarthByLine(line); return result; } @@ -593,7 +615,7 @@ class Camera extends Object3D { var cameraP = this.getPosition(); var dir = Vector.verticeMinusVertice(verticeInWorld, cameraP); var line = new Line(cameraP, dir); - var pickResult = this.getPickCartesianCoordInEarthByLine(line); + var pickResult = this._getPickCartesianCoordInEarthByLine(line); if (pickResult.length > 0) { var pickVertice = pickResult[0]; var length2Vertice = MathUtils.getLengthFromVerticeToVertice(cameraP, verticeInWorld); diff --git a/src/world/Event.ts b/src/world/Event.ts index eaf3b49..6bffb78 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -56,7 +56,7 @@ const EventModule = { 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] + "]"); + console.log("单击点三维坐标:(" + pickResult[0].x + "," + pickResult[0].y + "," + pickResult[0].z + ");经纬度坐标:[" + this.dragGeo[0] + "," + this.dragGeo[1] + "]"); } this.canvas.addEventListener("mousemove", this.onMouseMoveListener, false); } diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index e906352..033086d 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -13,7 +13,7 @@ const Kernel = { globe: null, idCounter: 0, //Object3D对象的唯一标识 BASE_LEVEL: 6, //渲染的基准层级 - MAX_LEVEL: 14,//最大的渲染级别 + MAX_LEVEL: 17,//最大的渲染级别 EARTH_RADIUS: radius, MAX_PROJECTED_COORD: maxProjectedCoord, proxy: "" From e7ea85a32cc80a5934cd44042c1f491bca9f0f17 Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 21:25:23 +0800 Subject: [PATCH 071/109] update version to 0.3.0,#14 --- package.json | 2 +- versions.txt | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 01c3981..35357f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webglobe", - "version": "0.2.4", + "version": "0.3.0", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { diff --git a/versions.txt b/versions.txt index 80869e4..1f8dabf 100644 --- a/versions.txt +++ b/versions.txt @@ -94,4 +94,9 @@ 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这两个类。 \ No newline at end of file +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系列矩阵。 \ No newline at end of file From 35042aa734c42453e6b072e49e8140b620767b14 Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 22:22:26 +0800 Subject: [PATCH 072/109] udpate Camera.ts,#14 --- src/world/Camera.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index cb4bb85..59e184a 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -413,18 +413,17 @@ class Camera extends Object3D { //don't call setLevel method because it will update CURRENT_LEVEL // newCamera._updatePositionByLevel(level); - this._animateToCamera(newCamera, () => { + this._animateToCamera(newCamera.matrix.getPosition(), () => { this.level = level; }); } - private _animateToCamera(newCamera: Camera, cb: () => void) { + private _animateToCamera(newPosition: Vertice, cb: () => void) { if (this.isAnimating()) { return; } this.animating = true; var oldPosition = this.getPosition(); - var newPosition = newCamera.matrix.getPosition(); var span = this.animationDuration; var singleSpan = 1000 / 60; var count = Math.floor(span / singleSpan); @@ -438,7 +437,8 @@ class Camera extends Object3D { } var a = timestap - start; if (a >= span) { - (Object).assign(this, newCamera._toJson()); + // (Object).assign(this, newCamera._toJson()); + this.setPosition(newPosition.x, newPosition.y, newPosition.z); this.animating = false; cb(); } else { From 9d65853cb456be548e48fdcdd75236d7404927fd Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 23:07:48 +0800 Subject: [PATCH 073/109] make Globe.animateToLevel() with version 0.3.0,#14 --- src/world/Camera.ts | 90 +++++++++++++++++++++++++-------------------- src/world/Event.ts | 4 +- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 59e184a..6dee2cc 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -19,6 +19,8 @@ class Camera extends Object3D { private level: number = -1; //当前渲染等级 + private animationLevel: number = -1;//非整数,表示缩放动画过程中的level + //旋转的时候,绕着视线与地球交点进行旋转 //定义抬头时,旋转角为正值 private isZeroPitch: boolean = true;//表示当前Camera视线有没有发生倾斜 @@ -58,13 +60,13 @@ class Camera extends Object3D { 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){ + 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, @@ -110,7 +112,7 @@ class Camera extends Object3D { // this._rawSetPerspectiveMatrix(this.fov, this.aspect, this.near, far); } - private _getMinimalFar(cameraPosition: Vertice): number{ + private _getMinimalFar(cameraPosition: Vertice): number { //重新计算far,保持far在满足正常需求情况下的最小值 //far值:视点与地球切面的距离 var distance2EarthOrigin = Vector.fromVertice(cameraPosition).getLength(); @@ -168,7 +170,9 @@ class Camera extends Object3D { private _updatePositionAndFov(cameraMatrix: Matrix): number { //是否满足near值,和fov没有关系,和position有关 //但是改变position的话,fov也要相应变动以满足对应的缩放效果 - const currentLevel = this.getLevel(); + const currentLevel = this.animating ? this.animationLevel : this.level; + + //safeLevel不是整数 var safeLevel = this._getSafeThresholdLevelForNear(); if (currentLevel > safeLevel) { @@ -193,7 +197,8 @@ class Camera extends Object3D { var thresholdNear = this.near * this.nearFactor; var pow2level = this.baseTheoryDistanceFromCamera2EarthSurface / thresholdNear; var level = (Math).log2(pow2level); - return Math.floor(level); + //return Math.floor(level); + return level; } /** @@ -271,13 +276,13 @@ class Camera extends Object3D { } //设置观察到的层级,不要在该方法中修改this.level的值 - private _updatePositionByLevel(level: number, cameraMatrix: Matrix) { + 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 intersect = intersects[0]; var theoryDistance2Interscet = this._getTheoryDistanceFromCamera2EarthSurface(level); var vector = cameraMatrix.getColumnZ(); vector.setLength(theoryDistance2Interscet); @@ -405,24 +410,21 @@ class Camera extends Object3D { return this.animating; } - animateToLevel(level: number): void { - if (!(Utils.isNonNegativeInteger(level))) { - throw "invalid level:" + level; - } - var newCamera = this._clone(); - //don't call setLevel method because it will update CURRENT_LEVEL - // newCamera._updatePositionByLevel(level); - - this._animateToCamera(newCamera.matrix.getPosition(), () => { - this.level = level; - }); - } - - private _animateToCamera(newPosition: Vertice, cb: () => void) { + animateToLevel(newLevel: number): void { if (this.isAnimating()) { return; } - this.animating = true; + + if (!(Utils.isNonNegativeInteger(newLevel))) { + throw "invalid level:" + newLevel; + } + var newCameraMatrix = this.matrix.clone(); + this._updatePositionByLevel(newLevel, newCameraMatrix); + var newPosition = newCameraMatrix.getPosition(); + + //don't call setLevel method because it will update CURRENT_LEVEL + // newCamera._updatePositionByLevel(newLevel); + var oldPosition = this.getPosition(); var span = this.animationDuration; var singleSpan = 1000 / 60; @@ -430,7 +432,11 @@ class Camera extends Object3D { 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.animationLevel = this.level; + this.animating = true; + var callback = (timestap: number) => { if (start < 0) { start = timestap; @@ -438,10 +444,14 @@ class Camera extends Object3D { var a = timestap - start; if (a >= span) { // (Object).assign(this, newCamera._toJson()); - this.setPosition(newPosition.x, newPosition.y, newPosition.z); + // this.setPosition(newPosition.x, newPosition.y, newPosition.z); + // this.animating = false; + //cb(); this.animating = false; - cb(); + this.animationLevel = -1; + this.setLevel(newLevel); } else { + this.animationLevel += deltaLevel; var p = this.getPosition(); this.setPosition(p.x + deltaX, p.y + deltaY, p.z + deltaZ); requestAnimationFrame(callback); @@ -450,22 +460,22 @@ class Camera extends Object3D { requestAnimationFrame(callback); } - private _clone(): Camera { - var camera: Camera = new Camera(); - (Object).assign(camera, this._toJson()); - return camera; - } + // private _clone(): Camera { + // var camera: Camera = new Camera(); + // (Object).assign(camera, this._toJson()); + // return camera; + // } - private _toJson(): any { - return { - near: this.near, - far: this.far, - fov: this.fov, - aspect: this.aspect, - matrix: this.matrix.clone(), - projMatrix: this.projMatrix.clone() - }; - } + // private _toJson(): any { + // return { + // near: this.near, + // far: this.far, + // fov: this.fov, + // aspect: this.aspect, + // matrix: this.matrix.clone(), + // projMatrix: this.projMatrix.clone() + // }; + // } private _look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { var cameraPntCopy = cameraPnt.clone(); @@ -501,7 +511,7 @@ class Camera extends Object3D { } //获取cameraMatrix视线与地球的交点 - private _getDirectionIntersectPointWithEarth(cameraMatrix: Matrix): Vertice[]{ + private _getDirectionIntersectPointWithEarth(cameraMatrix: Matrix): Vertice[] { var dir = cameraMatrix.getColumnZ().getOpposite(); var p = cameraMatrix.getPosition(); var line = new Line(p, dir); diff --git a/src/world/Event.ts b/src/world/Event.ts index 6bffb78..f11665e 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -154,8 +154,8 @@ const EventModule = { } var newLevel = globe.getLevel() + deltaLevel; if(newLevel >= 0){ - globe.setLevel(newLevel); - //globe.animateToLevel(newLevel); + //globe.setLevel(newLevel); + globe.animateToLevel(newLevel); } }, From a0f2cb4fab520a1428b6c8059f69be3f21bf8ceb Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 23:19:28 +0800 Subject: [PATCH 074/109] disable user interaction when animating,#14 --- src/world/Event.ts | 107 ++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/src/world/Event.ts b/src/world/Event.ts index f11665e..bfce024 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -49,51 +49,54 @@ 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); @@ -119,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); + 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); - 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); - } + this.moveLonLatToCanvas(lon, lat, absoluteX, absoluteY); } }, onMouseWheel(event: MouseWheelEvent) { var globe = Kernel.globe; - if (!globe) { + if (!globe || globe.isAnimating()) { return; } @@ -153,7 +158,7 @@ const EventModule = { deltaLevel = -parseInt((delta / 3)); } var newLevel = globe.getLevel() + deltaLevel; - if(newLevel >= 0){ + if (newLevel >= 0) { //globe.setLevel(newLevel); globe.animateToLevel(newLevel); } @@ -165,7 +170,7 @@ const EventModule = { */ onKeyDown(event: KeyboardEvent) { var globe = Kernel.globe; - if (!globe) { + if (!globe || globe.isAnimating()) { return; } @@ -173,10 +178,10 @@ const EventModule = { var camera = globe.camera; var keyNum = event.keyCode !== undefined ? event.keyCode : event.which; //上、下、左、右:38、40、37、39 - if(keyNum === 38){ + if (keyNum === 38) { //向上键 camera.setDeltaPitch(DELTA_PITCH); - }else if(keyNum === 40){ + } else if (keyNum === 40) { //向下键 camera.setDeltaPitch(-DELTA_PITCH); } From 0fe8c831192922e9c2643c5c5359fe5d263e146d Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 29 Nov 2016 23:23:10 +0800 Subject: [PATCH 075/109] update version to 0.3.1 --- package.json | 2 +- versions.txt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 35357f7..5a5b5d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webglobe", - "version": "0.3.0", + "version": "0.3.1", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { diff --git a/versions.txt b/versions.txt index 1f8dabf..da58fb2 100644 --- a/versions.txt +++ b/versions.txt @@ -99,4 +99,6 @@ 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系列矩阵。 \ No newline at end of file + 该版本提高了深度值的精度,基本解决了z值精度问题。在update()方法中会计算projViewMatrixForDraw系列矩阵。 + +0.3.1 使得Globe.animateToLevel()可以在0.3.0的版本上运行,解决办法是引入了camera.animationLevel,其值是非整数。 \ No newline at end of file From 352df4b236e2c3545346c6c2904c31fb568eef80 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 30 Nov 2016 09:50:46 +0800 Subject: [PATCH 076/109] remove some comments --- src/world/Camera.ts | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 6dee2cc..e1d94f9 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -153,7 +153,7 @@ class Camera extends Object3D { //计算newFar var newPosition = this.matrixForDraw.getPosition(); - var newFar = this.far; //this._getMinimalFar(newPosition); + var newFar = this.far; //this._getMinimalFar(newPosition); //根据newFov和newFar重新计算 this.projMatrixForDraw = new Matrix(); @@ -422,9 +422,6 @@ class Camera extends Object3D { this._updatePositionByLevel(newLevel, newCameraMatrix); var newPosition = newCameraMatrix.getPosition(); - //don't call setLevel method because it will update CURRENT_LEVEL - // newCamera._updatePositionByLevel(newLevel); - var oldPosition = this.getPosition(); var span = this.animationDuration; var singleSpan = 1000 / 60; @@ -443,10 +440,6 @@ class Camera extends Object3D { } var a = timestap - start; if (a >= span) { - // (Object).assign(this, newCamera._toJson()); - // this.setPosition(newPosition.x, newPosition.y, newPosition.z); - // this.animating = false; - //cb(); this.animating = false; this.animationLevel = -1; this.setLevel(newLevel); @@ -460,23 +453,6 @@ class Camera extends Object3D { requestAnimationFrame(callback); } - // private _clone(): Camera { - // var camera: Camera = new Camera(); - // (Object).assign(camera, this._toJson()); - // return camera; - // } - - // private _toJson(): any { - // return { - // near: this.near, - // far: this.far, - // fov: this.fov, - // aspect: this.aspect, - // matrix: this.matrix.clone(), - // projMatrix: this.projMatrix.clone() - // }; - // } - private _look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { var cameraPntCopy = cameraPnt.clone(); var targetPntCopy = targetPnt.clone(); From 035e241d40f58d3013dd00fba47abb08b8c09aa3 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 30 Nov 2016 10:20:57 +0800 Subject: [PATCH 077/109] update MAX_LEVEL to 15 --- src/world/Kernel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index 033086d..69eb736 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -13,7 +13,7 @@ const Kernel = { globe: null, idCounter: 0, //Object3D对象的唯一标识 BASE_LEVEL: 6, //渲染的基准层级 - MAX_LEVEL: 17,//最大的渲染级别 + MAX_LEVEL: 15,//最大的渲染级别 EARTH_RADIUS: radius, MAX_PROJECTED_COORD: maxProjectedCoord, proxy: "" From 0ce8a06cec6d9584163ab6e78ac4f5747a729c80 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 30 Nov 2016 12:41:49 +0800 Subject: [PATCH 078/109] call Math.asin() and Math.acos() safely,#20 --- src/world/Event.ts | 2 +- src/world/math/Math.ts | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/world/Event.ts b/src/world/Event.ts index bfce024..2ab4743 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -59,7 +59,7 @@ const EventModule = { 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] + "]"); + //console.log("单击点三维坐标:(" + pickResult[0].x + "," + pickResult[0].y + "," + pickResult[0].z + ");经纬度坐标:[" + this.dragGeo[0] + "," + this.dragGeo[1] + "]"); } this.canvas.addEventListener("mousemove", this.onMouseMoveListener, false); }, diff --git a/src/world/math/Math.ts b/src/world/math/Math.ts index 34bed55..462a5be 100644 --- a/src/world/math/Math.ts +++ b/src/world/math/Math.ts @@ -33,6 +33,9 @@ const MathUtils = { if(value > 1){ value = 1; } + if(value < -1){ + value = -1; + } return Math.asin(value); }, @@ -40,6 +43,9 @@ const MathUtils = { if(value > 1){ value = 1; } + if(value < -1){ + value = -1; + } return Math.acos(value); }, @@ -375,26 +381,23 @@ const MathUtils = { 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 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); + var radianLog = this.asinSafely(sin1); if(sin1 >= 0){ //经度在[0,π] if(cos1 >= 0){ From cbd9c526812b0d528e5d30016f3ed3aee67bfbde Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 30 Nov 2016 18:55:58 +0800 Subject: [PATCH 079/109] optimize Camera.update() method and update version to 0.3.2,#21 --- package.json | 2 +- src/world/Camera.ts | 61 ++++++++++++++++++++++++++-------------- src/world/math/Matrix.ts | 24 ++++++++++++---- versions.txt | 4 ++- 4 files changed, 63 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 5a5b5d0..7a33dc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webglobe", - "version": "0.3.1", + "version": "0.3.2", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { diff --git a/src/world/Camera.ts b/src/world/Camera.ts index e1d94f9..e96fb62 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -17,14 +17,20 @@ class Camera extends Object3D { private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; private readonly maxPitch = 40; - private level: number = -1; //当前渲染等级 - - private animationLevel: number = -1;//非整数,表示缩放动画过程中的level - //旋转的时候,绕着视线与地球交点进行旋转 //定义抬头时,旋转角为正值 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;//获取投影矩阵与视点矩阵的乘积 @@ -36,11 +42,6 @@ class Camera extends Object3D { private animating: boolean = false; - Enum: any = { - EARTH_FULL_OVERSPREAD_SCREEN: "EARTH_FULL_OVERSPREAD_SCREEN", //Canvas内全部被地球充满 - EARTH_NOT_FULL_OVERSPREAD_SCREEN: "EARTH_NOT_FULL_OVERSPREAD_SCREEN" //Canvas没有全部被地球充满 - }; - //this.near一旦初始化之后就不应该再修改 //this.far可以动态计算 //this.aspect在Viewport改变后重新计算 @@ -48,6 +49,8 @@ class Camera extends Object3D { 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(); @@ -121,11 +124,27 @@ class Camera extends Object3D { return far; } - //更新各种矩阵,保守起见,可以在每帧绘制之前调用 - //理论上只在用户交互的时候调用就可以 - update(): void { - this._normalUpdate(); - this._updateProjViewMatrixForDraw(); + //更新各种矩阵,理论上只在用户交互的时候调用就可以 + 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); + } + + 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 { @@ -168,9 +187,8 @@ class Camera extends Object3D { //返回更新后的fov值,如果返回结果 < 0,说明无需更新fov private _updatePositionAndFov(cameraMatrix: Matrix): number { - //是否满足near值,和fov没有关系,和position有关 - //但是改变position的话,fov也要相应变动以满足对应的缩放效果 - const currentLevel = this.animating ? this.animationLevel : this.level; + //是否满足near值,和fov没有关系,和position有关,但是改变position的话,fov也要相应变动以满足对应的缩放效果 + const currentLevel = this.animating ? this.realLevel : this.level; //safeLevel不是整数 var safeLevel = this._getSafeThresholdLevelForNear(); @@ -260,8 +278,9 @@ class Camera extends Object3D { return; } var isLevelChanged = this._updatePositionByLevel(level, this.matrix); - //不要在this._setLevel()方法中更新this.level,因为这会影响animateToLevel()方法 + //不要在this._updatePositionByLevel()方法中更新this.level,因为这会影响animateToLevel()方法 this.level = level; + this.realLevel = level; Kernel.globe.refresh(); } @@ -431,7 +450,7 @@ class Camera extends Object3D { var deltaZ = (newPosition.z - oldPosition.z) / count; var deltaLevel = (newLevel - this.level) / count; var start: number = -1; - this.animationLevel = this.level; + this.realLevel = this.level; this.animating = true; var callback = (timestap: number) => { @@ -441,10 +460,10 @@ class Camera extends Object3D { var a = timestap - start; if (a >= span) { this.animating = false; - this.animationLevel = -1; + this.realLevel = newLevel; this.setLevel(newLevel); } else { - this.animationLevel += deltaLevel; + this.realLevel += deltaLevel; var p = this.getPosition(); this.setPosition(p.x + deltaX, p.y + deltaY, p.z + deltaZ); requestAnimationFrame(callback); diff --git a/src/world/math/Matrix.ts b/src/world/math/Matrix.ts index 63d8274..948e69e 100644 --- a/src/world/math/Matrix.ts +++ b/src/world/math/Matrix.ts @@ -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, @@ -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 { diff --git a/versions.txt b/versions.txt index da58fb2..ab43142 100644 --- a/versions.txt +++ b/versions.txt @@ -101,4 +101,6 @@ 但是实际传递给shader用于绘图的是projViewMatrixForDraw。Camera.getPickCartesianCoordInEarthByCanvas()方法也是基于projViewMatrixForDraw系列矩阵的。 该版本提高了深度值的精度,基本解决了z值精度问题。在update()方法中会计算projViewMatrixForDraw系列矩阵。 -0.3.1 使得Globe.animateToLevel()可以在0.3.0的版本上运行,解决办法是引入了camera.animationLevel,其值是非整数。 \ No newline at end of file +0.3.1 使得Globe.animateToLevel()可以在0.3.0的版本上运行,解决办法是引入了camera.animationLevel,其值是非整数。 + +0.3.2 优化了Camera.update()方法,只有发生用户交互的情况下才实际进行计算。 \ No newline at end of file From 75e58f130b5dc8f6a2999f8b27215110e30fd827 Mon Sep 17 00:00:00 2001 From: iSpring Date: Wed, 30 Nov 2016 23:22:50 +0800 Subject: [PATCH 080/109] optimize Globe.refresh() method and update to version 0.3.3,#21 --- package.json | 2 +- src/world/Camera.ts | 48 ++++++++++++++++++++++++++++++- src/world/Definitions.d.ts | 12 ++++++++ src/world/Event.ts | 2 +- src/world/Globe.ts | 13 +++++++-- src/world/GraphicGroup.ts | 2 +- src/world/Renderer.ts | 2 +- src/world/graphics/Graphic.ts | 2 +- src/world/graphics/MeshGraphic.ts | 2 +- src/world/graphics/Poi.ts | 2 +- src/world/layers/TiledLayer.ts | 2 +- versions.txt | 4 ++- 12 files changed, 80 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 7a33dc8..41d876f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webglobe", - "version": "0.3.2", + "version": "0.3.3", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { diff --git a/src/world/Camera.ts b/src/world/Camera.ts index e96fb62..cc0ecb1 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -10,6 +10,48 @@ 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 = 600;//层级变化的动画周期是600毫秒 @@ -138,6 +180,10 @@ class Camera extends Object3D { 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) || @@ -937,4 +983,4 @@ class Camera extends Object3D { } } -export = Camera; \ No newline at end of file +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/Event.ts b/src/world/Event.ts index 2ab4743..22e38d8 100644 --- a/src/world/Event.ts +++ b/src/world/Event.ts @@ -2,7 +2,7 @@ import Kernel = require("./Kernel"); import MathUtils = require("./math/Math"); import Vector = require("./math/Vector"); -import Camera = require("./Camera"); +import Camera from "./Camera"; type MouseMoveListener = (e: MouseEvent) => {}; diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 5d73811..e51de78 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -2,7 +2,7 @@ import Kernel = require("./Kernel"); import Utils = require("./Utils"); import Renderer = require("./Renderer"); -import Camera = require("./Camera"); +import Camera, {CameraCore} from "./Camera"; import Scene = require("./Scene"); import TiledLayer = require("./layers/TiledLayer"); import SubTiledLayer = require("./layers/SubTiledLayer"); @@ -17,6 +17,7 @@ class Globe { scene: Scene = null; camera: Camera = null; tiledLayer: TiledLayer = null; + private cameraCore: CameraCore = null; constructor(canvas: HTMLCanvasElement) { Kernel.globe = this; @@ -132,12 +133,18 @@ class Globe { } } - refresh() { + refresh(force: boolean = false) { if (!this.tiledLayer || !this.scene || !this.camera) { return; } //先更新camera中的各种矩阵 - this.camera.update(); + this.camera.update(force); + var newCameraCore = this.camera.getCameraCore(); + var isNeedRefresh = force || !newCameraCore.equals(this.cameraCore); + this.cameraCore = newCameraCore; + if(!isNeedRefresh){ + return; + } var level = this.getLevel() + 3; this.tiledLayer.updateSubLayerCount(level); var options = { diff --git a/src/world/GraphicGroup.ts b/src/world/GraphicGroup.ts index 83bef4e..67f49a6 100644 --- a/src/world/GraphicGroup.ts +++ b/src/world/GraphicGroup.ts @@ -1,7 +1,7 @@ /// import Kernel = require("./Kernel"); import Graphic = require("./graphics/Graphic"); -import Camera = require("./Camera"); +import Camera from "./Camera"; type Drawable = Graphic | GraphicGroup; diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 8105eb4..78084dd 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -2,7 +2,7 @@ import Kernel = require("./Kernel"); import EventUtils = require("./Event"); import Scene = require("./Scene"); -import Camera = require("./Camera"); +import Camera from "./Camera"; import {WebGLRenderingContextExtension, WebGLProgramExtension} from "./Definitions"; class Renderer { diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts index af08ed8..8abfe33 100644 --- a/src/world/graphics/Graphic.ts +++ b/src/world/graphics/Graphic.ts @@ -5,7 +5,7 @@ import Geometry = require("../geometries/Geometry"); import Material = require("../materials/Material"); import Program = require("../Program"); import ProgramUtils = require("../ProgramUtils"); -import Camera = require("../Camera"); +import Camera from "../Camera"; interface GraphicOptions{ geometry: Geometry; diff --git a/src/world/graphics/MeshGraphic.ts b/src/world/graphics/MeshGraphic.ts index c05353e..3792a15 100644 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/world/graphics/MeshGraphic.ts @@ -5,7 +5,7 @@ import Program = require("../Program"); import Graphic = require("./Graphic"); import Mesh = require("../geometries/Mesh"); import MeshTextureMaterial = require("../materials/MeshTextureMaterial"); -import Camera = require("../Camera"); +import Camera from "../Camera"; const vs = ` diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index c3d7b96..da94e61 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -5,7 +5,7 @@ import Graphic = require('./Graphic'); import Marker = require('../geometries/Marker'); import PoiMaterial = require('../materials/PoiMaterial'); import Program = require("../Program"); -import Camera = require("../Camera"); +import Camera from "../Camera"; const vs = ` diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index d1bd0ac..afa39f6 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -2,7 +2,7 @@ import Kernel = require('../Kernel'); import GraphicGroup = require('../GraphicGroup'); import SubTiledLayer = require('./SubTiledLayer'); -import Camera = require('../Camera'); +import Camera from '../Camera'; abstract class TiledLayer extends GraphicGroup { diff --git a/versions.txt b/versions.txt index ab43142..7880673 100644 --- a/versions.txt +++ b/versions.txt @@ -103,4 +103,6 @@ 0.3.1 使得Globe.animateToLevel()可以在0.3.0的版本上运行,解决办法是引入了camera.animationLevel,其值是非整数。 -0.3.2 优化了Camera.update()方法,只有发生用户交互的情况下才实际进行计算。 \ No newline at end of file +0.3.2 优化了Camera.update()方法,只有发生用户交互的情况下才实际进行计算。 + +0.3.3 优化了Globe.refresh()方法,只有发生用具交互的情况下才重新计算可见切片。 \ No newline at end of file From 177a1ca2e2b2bd12cfb74b4fcea9a3412bd9ddcb Mon Sep 17 00:00:00 2001 From: iSpring Date: Wed, 30 Nov 2016 23:46:52 +0800 Subject: [PATCH 081/109] clear color to black --- src/world/Renderer.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index 78084dd..c63e1b7 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -58,12 +58,13 @@ class Renderer { } render(scene: Scene, camera: Camera) { - Kernel.gl.viewport(0, 0, Kernel.canvas.width, Kernel.canvas.height); - Kernel.gl.clear(Kernel.gl.COLOR_BUFFER_BIT | Kernel.gl.DEPTH_BUFFER_BIT); - Kernel.gl.enable(Kernel.gl.DEPTH_TEST); - Kernel.gl.depthFunc(Kernel.gl.LEQUAL); - Kernel.gl.depthMask(true); - //update viewMatrix, projMatrix and projViewMatrix + 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); } From 62e361a45352263aff5baf535fc428d3acf7ff0c Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 1 Dec 2016 12:59:25 +0800 Subject: [PATCH 082/109] update versions.txt --- versions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.txt b/versions.txt index 7880673..80557a2 100644 --- a/versions.txt +++ b/versions.txt @@ -105,4 +105,4 @@ 0.3.2 优化了Camera.update()方法,只有发生用户交互的情况下才实际进行计算。 -0.3.3 优化了Globe.refresh()方法,只有发生用具交互的情况下才重新计算可见切片。 \ No newline at end of file +0.3.3 优化了Globe.refresh()方法,只有发生用户交互的情况下才重新计算可见切片。 \ No newline at end of file From b10c0357643448ddde5e1bf41d8abf2452a1c813 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 1 Dec 2016 13:12:31 +0800 Subject: [PATCH 083/109] add Atmosphere files,#16 --- src/world/geometries/Atmosphere.ts | 13 +++++ src/world/graphics/Atmosphere.ts | 84 +++++++++++++++++++++++++++++ src/world/images/atmosphere.png | Bin 0 -> 112776 bytes src/world/images/atmosphere64.png | Bin 0 -> 2767 bytes 4 files changed, 97 insertions(+) create mode 100644 src/world/geometries/Atmosphere.ts create mode 100644 src/world/graphics/Atmosphere.ts create mode 100644 src/world/images/atmosphere.png create mode 100644 src/world/images/atmosphere64.png diff --git a/src/world/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts new file mode 100644 index 0000000..4fca050 --- /dev/null +++ b/src/world/geometries/Atmosphere.ts @@ -0,0 +1,13 @@ +/// + +import Vertice = require("./MeshVertice"); +import Triangle = require("./Triangle"); +import Mesh = require("./Mesh"); + +class Atmosphere extends Mesh { + constructor(public vertices: Vertice[], public triangles: Triangle[]) { + super(); + } +} + +export = Atmosphere; \ 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..af33552 --- /dev/null +++ b/src/world/graphics/Atmosphere.ts @@ -0,0 +1,84 @@ +/// + +import Kernel = require("../Kernel"); +import Graphic = require('./Graphic'); +import Marker = require('../geometries/Marker'); +import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); +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 +const fs = +` +precision mediump float; +uniform sampler2D uSampler; + +void main() +{ + gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y)); +} +`; + +class Atmosphere extends Graphic { + constructor(public geometry: Marker, public material: MeshTextureMaterial){ + 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 = Atmosphere; \ No newline at end of file diff --git a/src/world/images/atmosphere.png b/src/world/images/atmosphere.png new file mode 100644 index 0000000000000000000000000000000000000000..4f0a404aef2cf914a8a14503fd0b326f7c70a573 GIT binary patch literal 112776 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
    TQI2ry3u*GGJ_P}r> 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 literal 0 HcmV?d00001 diff --git a/src/world/images/atmosphere64.png b/src/world/images/atmosphere64.png new file mode 100644 index 0000000000000000000000000000000000000000..f586c15e574af3dbc02ee45e4087124d4784af8a GIT binary patch literal 2767 zcmV;=3NZDFP)Nkl>0>|;WJ6b9UDWvy=^xiw^JtP4MK~sbTAt)V@P5==?Q|UH3b^!|#s(LCSVn+o; zJrRQlN>rp~Z)Ud5?#{lu`=a;H`8=QJ`PM45iYW=M}OJYBEQW3DwoUy-td0~EnY-* z03^@b;Gu)N0dBG)AOQXWPUC+Y{PgMLYX%?L)}nE_0EB>Ey^vtD_3k~ufFLsjnjt_J z_}6e`uU=LW`dNn?5Oy0v4G7WJix*4?f&a9yjO}Tu5JRaxR#wp_^%O*LITY|^{~eWf_oZ790Bv1JZAYg^hh03G}w=o8Q~2AIBk&MQ+pjSUKd9 zXwMX^9de9u94MB?jwYL^D$v`~zQoejsn`@NTc;xVS*jmRp=|5dw_kx!@gg3B*@QFb;h|3vvu?w##WbFqi7ck?3_HCIE}KclUIXSot(Yu&2*Pu_Rd~+>H+MX zeOmgt`ZgOhQd57y2%;LiY3?@6#k)x_;Na#rMkzq#?l&4k&-pD8AZ`~aM;E_)baV9| z#j__$a+GbT#a=9?wz)IBJSpJh5jalfJ_GLM$j>VLX~t^u3}_X|&opio}ViuLJ zxLr8FJs|o)PZjP;3k?g5UZ7vhd{z`EMp}PT47uB+qBQZB3p|73+NEOJy#~ZC;Y;H| zkewGB6Zep&KtfR5A|v4M;X#j}7+f{LDU!S@I=_gt zb(+E@*6QUWNL^!uZ+Plzlfw9>t-?Djc{O{XX{)4V0l$chCs~Dr-|f)WA(_wFO3JZ@djp z$@xcTiXu&j%6d{ET6%`Rz9;yCpxE3Us^apt>op)QZ(C4Y4s6agBrry&*xapB19iyR zqAFS`01Ar9fft#x88whQBOo}w;CbjdqEMcO{Aa~#3rWa-1{n}1cvf~+7hmu+7MrIO zpGSR;E-yGXe}~D-<$&OXqFtd0MLUD33ME1k3tys&H>H4;Q1GHLg)an4L~7-JQkaJDw90+(Yw6K}n}T&nmDBN; z4GSVOs^3z`ta%edJH6^nLo%x0knA)yRq(y)!)&V#n<{s(4hz&-~}F(1JT(- zk3?q=5yWKQ@vcc(O3_(&yhAsH=50Nqc-ce#E$mFVjmWBbOCf{9Z4j3`{Ft`7V{v(P z@5kjB6Q4W$C~i*O(b!xl$29ovd-Ae|>2U;!MF#R}HG#y!#*bMIq>pe5V9nC_p{PM3 zf$lg_PW|zO{CctZ!ubQCCf$X_^YS?P^~XpcrL^TlN@+8v<#UM=b;%0NpHUSP#YTRX zQqufsO3BDill3SfiWE10l3YCE6R~PWevFy~ixrJv6?3=^GAhTO%BZ~eBrhGQ;M=NH zIrb}|YRXYt(R*KF<>F4`p9$wNu&gve>lZ@H(dLvB6^s_7mybC?H~K^>Y$`5P#x|{N z^yeUZ@T70DYe*ACgD0IaDhsKZ^fh#5V9i9>iC@dhgWuycYl1%2$oSKlP*%-^Q}An? zVk?y?R|Qp%KS=}f>SmtJ8#aSgH{*NObfW5VQHQ|}oADo|2Z$1CMR~Q;zsps4fNm)2 z^lx)(r>V-3`UV?n8YpgDaK5N%VP{d(f=;o71}XUdk9><8=PNacif|X4E5aMK4Yuu^ zLOt2}KOlv07os+_shB6K=e3O+t=F&)z4r^OphjY?pc;4GCDiJBVKKDVULsZT#v*Gj zRgYbBQK5CUL{;mmi(vS)9an}=+kR#E{c5%e>R5jWrt;lNu>~83OhJ3=?+Cf!=Sowy z{D#2y(7Ujk=_YT&YI##Ob=6MZ$aPcKP{Bs2NgTYN>H;I?y?mo-?rxb~*xhq?T_?9( zlVGRP9NcTz9J)rqOM<4^JFl`{y4tAlBI!@0Vb+UR8)m=or@}1cd90o{6ZM5Fu$JG4W#A4y%J#RV;^=;U4NW0Ws1ZH`mK|ij2)v}E1Iul#^5BfX5AWtJklMeV!4XTY-`{n7pO@!uJ0`m z*Ok7x&K#iFe@1E_?`u{EVejc{60e^U?LkhmUPHdR#=MGM#lWfGnC>qxcQb#dFOXm9 zPDtEh{c?->oZ>U&lIT;yC&)$C$ApiNpIIMrjw2UX;FlXr_r>cBbMeMsT+&gZ|{*((j51f5-s%wYy=N{|7fI{tNPL Vm@HtQQ1$=-002ovPDHLkV1ltHNX-BM literal 0 HcmV?d00001 From 78e46dde42508250fbeefe6683c0a568385315d2 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 1 Dec 2016 15:54:10 +0800 Subject: [PATCH 084/109] update Atmosphere.ts,#16 --- src/world/geometries/Atmosphere.ts | 60 ++++++++++++++++++++++++++++- src/world/geometries/MeshVertice.ts | 5 ++- src/world/geometries/Triangle.ts | 4 ++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/world/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts index 4fca050..0df5890 100644 --- a/src/world/geometries/Atmosphere.ts +++ b/src/world/geometries/Atmosphere.ts @@ -1,13 +1,69 @@ /// -import Vertice = require("./MeshVertice"); +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 { - constructor(public vertices: Vertice[], public triangles: Triangle[]) { + private readonly segment: number = 36; + private readonly radius1: number = Kernel.EARTH_RADIUS; + private readonly radius2: number = Kernel.EARTH_RADIUS * 1.1; + + constructor() { super(); } + + buildTriangles(){ + this.vertices = []; + this.triangles = []; + + var mat1 = new Matrix(); + mat1.setColumnTrans(0, this.radius1, 0); + var meshVertices1: MeshVertice[] = []; + + var mat2 = new Matrix(); + mat2.setColumnTrans(0, this.radius2, 0); + var meshVertices2: MeshVertice[] = []; + + var deltaRadian = Math.PI * 2 / this.segment; + + for(var i = 0; i < this.segment; i++){ + meshVertices1.push(new MeshVertice({ + i: i, + p: mat1.getPosition().getArray() + })); + + meshVertices2.push(new MeshVertice({ + i: this.segment + i, + p: mat2.getPosition().getArray() + })); + + //don't flip Y + if(i % 2 === 0){ + //meshVertices1[i] left bottom + meshVertices1[i].uv = [0, 1]; + //meshVertices2[i] left top + meshVertices2[i].uv = [0, 0]; + }else{ + //meshVertices1[i] right bottom + meshVertices1[i].uv = [1, 1]; + //meshVertices2[i] right top + meshVertices2[i].uv = [1, 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); + } + } } export = Atmosphere; \ No newline at end of file diff --git a/src/world/geometries/MeshVertice.ts b/src/world/geometries/MeshVertice.ts index e368413..8bc9f64 100644 --- a/src/world/geometries/MeshVertice.ts +++ b/src/world/geometries/MeshVertice.ts @@ -7,11 +7,12 @@ class MeshVertice{ i:number; constructor(args:any){ + this.i = args.i;//index this.p = args.p;//[x,y,z] - this.n = args.n;//[x,y,z] this.uv = args.uv;//[s,t] + + this.n = args.n;//[x,y,z] this.c = args.c;//[r,g,b] - this.i = args.i;//index } } diff --git a/src/world/geometries/Triangle.ts b/src/world/geometries/Triangle.ts index 3d04629..b4081ad 100644 --- a/src/world/geometries/Triangle.ts +++ b/src/world/geometries/Triangle.ts @@ -8,6 +8,10 @@ class Triangle{ 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 From 051ffc5fbde1c7f3837f5bf9c95e3328b8d59801 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 1 Dec 2016 16:16:00 +0800 Subject: [PATCH 085/109] update Atmosphere.ts,#16 --- src/world/geometries/Atmosphere.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/world/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts index 0df5890..8d4aaa9 100644 --- a/src/world/geometries/Atmosphere.ts +++ b/src/world/geometries/Atmosphere.ts @@ -28,31 +28,31 @@ class Atmosphere extends Mesh { mat2.setColumnTrans(0, this.radius2, 0); var meshVertices2: MeshVertice[] = []; - var deltaRadian = Math.PI * 2 / this.segment; + 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() + p: mat1.getPosition().getArray(), + uv: [u, 1] })); + //big radius, v is always 0 meshVertices2.push(new MeshVertice({ i: this.segment + i, - p: mat2.getPosition().getArray() + p: mat2.getPosition().getArray(), + uv: [u, 0] })); - //don't flip Y - if(i % 2 === 0){ - //meshVertices1[i] left bottom - meshVertices1[i].uv = [0, 1]; - //meshVertices2[i] left top - meshVertices2[i].uv = [0, 0]; - }else{ - //meshVertices1[i] right bottom - meshVertices1[i].uv = [1, 1]; - //meshVertices2[i] right top - meshVertices2[i].uv = [1, 0]; - + if(i > 0){ var vLeftTop = meshVertices2[i-1]; var vLeftBottom = meshVertices1[i-1]; var vRightTop = meshVertices2[i]; From a88e3726305e2ffd9a9fd26d3507c6f221fca495 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 1 Dec 2016 18:15:14 +0800 Subject: [PATCH 086/109] remove ProgramUtils.ts and add static method getProgram() for Program class --- src/world/Program.ts | 28 ++++++++++++++++++++++++++-- src/world/ProgramUtils.ts | 33 --------------------------------- src/world/graphics/Graphic.ts | 3 +-- 3 files changed, 27 insertions(+), 37 deletions(-) delete mode 100644 src/world/ProgramUtils.ts diff --git a/src/world/Program.ts b/src/world/Program.ts index 831b59e..ae2ad6a 100644 --- a/src/world/Program.ts +++ b/src/world/Program.ts @@ -1,12 +1,14 @@ /// import Kernel = require("./Kernel"); +import Graphic = require("./graphics/Graphic"); class Program{ ready: boolean = false; - activeInfosObject:any; + activeInfosObject: any; program: WebGLProgram; - static currentProgram:Program; + 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 @@ -15,6 +17,28 @@ class Program{ 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); diff --git a/src/world/ProgramUtils.ts b/src/world/ProgramUtils.ts deleted file mode 100644 index 5c74fd4..0000000 --- a/src/world/ProgramUtils.ts +++ /dev/null @@ -1,33 +0,0 @@ -/// - -import Program = require("./Program"); -import Graphic = require("./graphics/Graphic"); - -const programs:Program[] = []; - -const ProgramUtils = { - - getProgram(graphic: Graphic){ - var program:Program = null; - - var programType = graphic.getProgramType(); - - programs.some(function(item){ - if(item.type === graphic.getProgramType()){ - program = item; - return true; - }else{ - return false; - } - }); - - if(!program){ - program = graphic.createProgram(); - programs.push(program); - } - - return program; - } -}; - -export = ProgramUtils; \ No newline at end of file diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts index 8abfe33..e5fe307 100644 --- a/src/world/graphics/Graphic.ts +++ b/src/world/graphics/Graphic.ts @@ -4,7 +4,6 @@ import Kernel = require("../Kernel"); import Geometry = require("../geometries/Geometry"); import Material = require("../materials/Material"); import Program = require("../Program"); -import ProgramUtils = require("../ProgramUtils"); import Camera from "../Camera"; interface GraphicOptions{ @@ -23,7 +22,7 @@ abstract class Graphic{ constructor(public geometry: Geometry, public material: Material){ this.id = ++Kernel.idCounter; this.parent = null; - this.program = ProgramUtils.getProgram(this); + this.program = Program.getProgram(this); } setVisible(visible: boolean){ From 0494e76b639a63d655874435fe75fc4a5d0240a5 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 1 Dec 2016 18:37:50 +0800 Subject: [PATCH 087/109] update Atmosphere.ts --- src/world/geometries/Atmosphere.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/world/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts index 8d4aaa9..6720615 100644 --- a/src/world/geometries/Atmosphere.ts +++ b/src/world/geometries/Atmosphere.ts @@ -10,10 +10,11 @@ import Matrix = require("../math/Matrix"); class Atmosphere extends Mesh { private readonly segment: number = 36; private readonly radius1: number = Kernel.EARTH_RADIUS; - private readonly radius2: number = Kernel.EARTH_RADIUS * 1.1; + private readonly radius2: number = Kernel.EARTH_RADIUS * 1.2; constructor() { super(); + this.buildTriangles(); } buildTriangles(){ @@ -32,7 +33,7 @@ class Atmosphere extends Mesh { var deltaS: number = 1.0 / this.segment; var u: number = 0; - for(var i = 0; i < this.segment; i++){ + for(var i = 0; i <= this.segment; i++){ u = deltaS * i; if(u > 1){ u = 1; @@ -47,7 +48,7 @@ class Atmosphere extends Mesh { //big radius, v is always 0 meshVertices2.push(new MeshVertice({ - i: this.segment + i, + i: this.segment + 1 + i, p: mat2.getPosition().getArray(), uv: [u, 0] })); From f93b19703bcf9d0727354c9871395b46fcee6693 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Thu, 1 Dec 2016 19:13:53 +0800 Subject: [PATCH 088/109] update Atmosphere,#16 --- src/world/graphics/Atmosphere.ts | 36 +++++--------------------------- src/world/graphics/Graphic.ts | 7 ++++--- 2 files changed, 9 insertions(+), 34 deletions(-) diff --git a/src/world/graphics/Atmosphere.ts b/src/world/graphics/Atmosphere.ts index af33552..90e7b74 100644 --- a/src/world/graphics/Atmosphere.ts +++ b/src/world/graphics/Atmosphere.ts @@ -1,46 +1,20 @@ /// import Kernel = require("../Kernel"); -import Graphic = require('./Graphic'); +import MeshGraphic = require('./MeshGraphic'); import Marker = require('../geometries/Marker'); import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); import Program = require("../Program"); import Camera from "../Camera"; +import AtmosphereGeometry = require("../geometries/Atmosphere"); -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 -const fs = -` -precision mediump float; -uniform sampler2D uSampler; - -void main() -{ - gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y)); -} -`; - -class Atmosphere extends Graphic { - constructor(public geometry: Marker, public material: MeshTextureMaterial){ +class Atmosphere extends MeshGraphic { + constructor(public geometry: AtmosphereGeometry, public material: MeshTextureMaterial){ super(geometry, material); } - createProgram(){ - return new Program(this.getProgramType(), vs, fs); - } - onDraw(camera: Camera){ + super.onDraw(camera); // var gl = Kernel.gl; // //gl.disable(gl.DEPTH_TEST); diff --git a/src/world/graphics/Graphic.ts b/src/world/graphics/Graphic.ts index e5fe307..24c8b47 100644 --- a/src/world/graphics/Graphic.ts +++ b/src/world/graphics/Graphic.ts @@ -5,18 +5,19 @@ 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: any; + parent: GraphicGroup; visible?: boolean; } abstract class Graphic{ - id:number; + id: number; visible: boolean = true; - parent: any; + parent: GraphicGroup; program: Program; constructor(public geometry: Geometry, public material: Material){ From bf742194a9de4d9ed108aa4050a91ef978e7b1f0 Mon Sep 17 00:00:00 2001 From: iSpring Date: Thu, 1 Dec 2016 22:11:22 +0800 Subject: [PATCH 089/109] make Atmosphere basically work,#16 --- main.js | 8 ++++++-- src/world/Globe.ts | 4 ++-- src/world/geometries/Atmosphere.ts | 8 +++++--- src/world/graphics/Atmosphere.ts | 15 ++++++++++----- src/world/graphics/Tile.ts | 4 ++-- src/world/layers/SubTiledLayer.ts | 2 +- src/world/materials/MeshTextureMaterial.ts | 2 +- 7 files changed, 27 insertions(+), 16 deletions(-) diff --git a/main.js b/main.js index 075db09..feda64a 100644 --- a/main.js +++ b/main.js @@ -1,9 +1,9 @@ window.onload = function() { require(["world/Kernel", "world/Globe", "world/layers/BingTiledLayer", "world/layers/NokiaTiledLayer", "world/layers/OsmTiledLayer", - "world/layers/SosoTiledLayer", "world/layers/TiandituTiledLayer", "world/layers/GoogleTiledLayer", + "world/layers/SosoTiledLayer", "world/layers/TiandituTiledLayer", "world/layers/GoogleTiledLayer", "world/graphics/Atmosphere", "world/layers/PoiLayer"], function(Kernel, Globe, BingTiledLayer, NokiaTiledLayer, OsmTiledLayer, SosoTiledLayer, TiandituTiledLayer, GoogleTiledLayer, - PoiLayer) { + Atmosphere, PoiLayer) { window.Kernel = Kernel; @@ -15,6 +15,10 @@ mapSelector.onchange = changeTiledLayer; changeTiledLayer(); + var atmosphere = Atmosphere.getInstance(); + + window.globe.scene.add(atmosphere); + var poiLayer = new PoiLayer(); window.globe.scene.add(poiLayer); } diff --git a/src/world/Globe.ts b/src/world/Globe.ts index e51de78..3238236 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -24,7 +24,7 @@ class Globe { this.renderer = Kernel.renderer = new Renderer(canvas); this.scene = new Scene(); var radio = canvas.width / canvas.height; - this.camera = new Camera(30, radio, 1, Kernel.EARTH_RADIUS * 2); + this.camera = new Camera(30, radio, 1, Kernel.EARTH_RADIUS * 4); this.renderer.setScene(this.scene); this.renderer.setCamera(this.camera); this.setLevel(0); @@ -66,7 +66,7 @@ class Globe { url: "" }; args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); - var tile = Tile.getTile(args.level, args.row, args.column, args.url); + var tile = Tile.getInstance(args.level, args.row, args.column, args.url); subLayer1.add(tile); } } diff --git a/src/world/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts index 6720615..45ec7f4 100644 --- a/src/world/geometries/Atmosphere.ts +++ b/src/world/geometries/Atmosphere.ts @@ -8,9 +8,9 @@ import Vertice = require("../math/Vertice"); import Matrix = require("../math/Matrix"); class Atmosphere extends Mesh { - private readonly segment: number = 36; + private readonly segment: number = 360; private readonly radius1: number = Kernel.EARTH_RADIUS; - private readonly radius2: number = Kernel.EARTH_RADIUS * 1.2; + private readonly radius2: number = Kernel.EARTH_RADIUS * 1.01; constructor() { super(); @@ -29,7 +29,7 @@ class Atmosphere extends Mesh { mat2.setColumnTrans(0, this.radius2, 0); var meshVertices2: MeshVertice[] = []; - var deltaRadian: number = Math.PI * 2 / this.segment; + var deltaRadian: number = - Math.PI * 2 / this.segment; var deltaS: number = 1.0 / this.segment; var u: number = 0; @@ -64,6 +64,8 @@ class Atmosphere extends Mesh { mat1.worldRotateZ(deltaRadian); mat2.worldRotateZ(deltaRadian); } + + this.vertices.push(...meshVertices1, ...meshVertices2); } } diff --git a/src/world/graphics/Atmosphere.ts b/src/world/graphics/Atmosphere.ts index 90e7b74..b93a4e5 100644 --- a/src/world/graphics/Atmosphere.ts +++ b/src/world/graphics/Atmosphere.ts @@ -1,18 +1,23 @@ -/// +/// import Kernel = require("../Kernel"); import MeshGraphic = require('./MeshGraphic'); -import Marker = require('../geometries/Marker'); +import AtmosphereGeometry = require("../geometries/Atmosphere"); import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); -import Program = require("../Program"); import Camera from "../Camera"; -import AtmosphereGeometry = require("../geometries/Atmosphere"); class Atmosphere extends MeshGraphic { - constructor(public geometry: AtmosphereGeometry, public material: MeshTextureMaterial){ + private constructor(public geometry: AtmosphereGeometry, public material: MeshTextureMaterial){ super(geometry, material); } + static getInstance(): Atmosphere{ + var geometry = new AtmosphereGeometry(); + var imageUrl = "/WebGlobe/src/world/images/atmosphere64.png"; + var material = new MeshTextureMaterial(imageUrl, false); + return new Atmosphere(geometry, material); + } + onDraw(camera: Camera){ super.onDraw(camera); // var gl = Kernel.gl; diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index cde99c5..08efa88 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -151,11 +151,11 @@ class TileInfo { class Tile extends MeshGraphic { subTiledLayer: any; - constructor(public geometry: TileGeometry, public material: TileMaterial, public tileInfo: TileInfo) { + private constructor(public geometry: TileGeometry, public material: TileMaterial, public tileInfo: TileInfo) { super(geometry, material); } - static getTile(level: number, row: number, column: number, url: string) { + 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); } diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index 91c4ba9..f617053 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -96,7 +96,7 @@ class SubTiledLayer extends GraphicGroup { url: "" }; args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); - tile = Tile.getTile(args.level, args.row, args.column, args.url); + tile = Tile.getInstance(args.level, args.row, args.column, args.url); this.add(tile); } } diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts index 39e7f4e..cb52f2b 100644 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/world/materials/MeshTextureMaterial.ts @@ -13,7 +13,7 @@ class MeshTextureMaterial extends Material { private ready:boolean = false; private deleted: boolean = false; - constructor(imageOrUrl?: ImageType, public flipY: boolean = true) { + constructor(imageOrUrl: ImageType = null, public flipY: boolean = true) { super(); this.texture = Kernel.gl.createTexture(); if(imageOrUrl){ From 07e4174646c9aee4f086977afac7583f0df43e17 Mon Sep 17 00:00:00 2001 From: iSpring Date: Thu, 1 Dec 2016 23:24:33 +0800 Subject: [PATCH 090/109] rename some methods of Matrix and Object3D --- src/world/Camera.ts | 33 ++++++------ src/world/Object3D.ts | 83 +++++++++++++++++++----------- src/world/geometries/Atmosphere.ts | 4 +- src/world/graphics/Atmosphere.ts | 38 +------------- src/world/math/Matrix.ts | 80 ++++++++++++++-------------- 5 files changed, 111 insertions(+), 127 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index cc0ecb1..e850f78 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -349,10 +349,10 @@ class Camera extends Object3D { } var intersect = intersects[0]; var theoryDistance2Interscet = this._getTheoryDistanceFromCamera2EarthSurface(level); - var vector = cameraMatrix.getColumnZ(); + var vector = cameraMatrix.getVectorZ(); vector.setLength(theoryDistance2Interscet); var newCameraPosition = Vector.verticePlusVector(intersect, vector); - cameraMatrix.setColumnTrans(newCameraPosition.x, newCameraPosition.y, newCameraPosition.z); + cameraMatrix.setPosition(newCameraPosition); } setDeltaPitch(deltaPitch: number) { @@ -381,7 +381,7 @@ class Camera extends Object3D { //先不对this.matrix进行更新,对其拷贝进行更新 var matrix = this.matrix.clone(); //将matrix移动到交点位置 - matrix.setColumnTrans(intersect.x, intersect.y, intersect.z); + matrix.setPosition(intersect); //旋转 matrix.localRotateX(deltaRadian); //更新matrix的position @@ -414,7 +414,7 @@ class Camera extends Object3D { //计算夹角的正负 var crossVector = vectorOrigin2Intersect.cross(vectorIntersect2Camera); - var xAxisDirection = this.matrix.getColumnX() + var xAxisDirection = this.matrix.getVectorX() if (crossVector.dot(xAxisDirection)) { //正值 radian = Math.abs(radian); @@ -459,7 +459,7 @@ class Camera extends Object3D { } getLightDirection(): Vector { - var dirVertice = this.matrix.getColumnZ(); + var dirVertice = this.matrix.getVectorZ(); var direction = new Vector(-dirVertice.x, -dirVertice.y, -dirVertice.z); direction.normalize(); return direction; @@ -511,7 +511,7 @@ class Camera extends Object3D { } else { this.realLevel += deltaLevel; var p = this.getPosition(); - this.setPosition(p.x + deltaX, p.y + deltaY, p.z + deltaZ); + this.setPosition(new Vertice(p.x + deltaX, p.y + deltaY, p.z + deltaZ)); requestAnimationFrame(callback); } }; @@ -522,17 +522,20 @@ class Camera extends Object3D { 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 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.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.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(); @@ -553,7 +556,7 @@ class Camera extends Object3D { //获取cameraMatrix视线与地球的交点 private _getDirectionIntersectPointWithEarth(cameraMatrix: Matrix): Vertice[] { - var dir = cameraMatrix.getColumnZ().getOpposite(); + var dir = cameraMatrix.getVectorZ().getOpposite(); var p = cameraMatrix.getPosition(); var line = new Line(p, dir); var result = this._getPickCartesianCoordInEarthByLine(line); diff --git a/src/world/Object3D.ts b/src/world/Object3D.ts index 4be75e1..8e64ee5 100644 --- a/src/world/Object3D.ts +++ b/src/world/Object3D.ts @@ -8,93 +8,114 @@ class Object3D { protected matrix: Matrix; constructor() { - this.matrix = new Matrix(); + this.matrix = new Matrix(); } getMatrix(): Matrix{ - return this.matrix; + return this.matrix; } cloneMatrix(): Matrix{ - return this.matrix.clone(); + return this.matrix.clone(); } - //需要子类重写 - getPosition(): Vertice { - var position = this.matrix.getPosition(); - return position; + 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(); } - //需要子类重写 - setPosition(x: number, y: number, z: number): void { - this.matrix.setColumnTrans(x, y, z); + 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/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts index 45ec7f4..459424f 100644 --- a/src/world/geometries/Atmosphere.ts +++ b/src/world/geometries/Atmosphere.ts @@ -22,11 +22,11 @@ class Atmosphere extends Mesh { this.triangles = []; var mat1 = new Matrix(); - mat1.setColumnTrans(0, this.radius1, 0); + mat1.setPosition(new Vertice(0, this.radius1, 0)); var meshVertices1: MeshVertice[] = []; var mat2 = new Matrix(); - mat2.setColumnTrans(0, this.radius2, 0); + mat2.setPosition(new Vertice(0, this.radius2, 0)); var meshVertices2: MeshVertice[] = []; var deltaRadian: number = - Math.PI * 2 / this.segment; diff --git a/src/world/graphics/Atmosphere.ts b/src/world/graphics/Atmosphere.ts index b93a4e5..7d0522e 100644 --- a/src/world/graphics/Atmosphere.ts +++ b/src/world/graphics/Atmosphere.ts @@ -19,44 +19,8 @@ class Atmosphere extends MeshGraphic { } onDraw(camera: Camera){ + //根据Camera动态调整Atmosphere的matrix,使其一直垂直面向摄像机 super.onDraw(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); } } diff --git a/src/world/math/Matrix.ts b/src/world/math/Matrix.ts index 948e69e..d668e1f 100644 --- a/src/world/math/Matrix.ts +++ b/src/world/math/Matrix.ts @@ -51,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]); } @@ -306,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; @@ -336,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 { @@ -432,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指的是相对于模型坐标系中的向量 @@ -462,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); } }; From 1963bb6cc51b14428fa2a49335250df8f4139194 Mon Sep 17 00:00:00 2001 From: iSpring Date: Fri, 2 Dec 2016 00:06:22 +0800 Subject: [PATCH 091/109] update Atmosphere.onDraw() method to adjust its matrix every frame by Camera,#16 --- src/world/Camera.ts | 5 +++++ src/world/Globe.ts | 2 +- src/world/geometries/Atmosphere.ts | 2 +- src/world/graphics/Atmosphere.ts | 21 ++++++++++++++++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index e850f78..0908dfb 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -471,6 +471,11 @@ class Camera extends Object3D { return length2EarthSurface; } + getDistance2EarthOrigin(): number{ + var position = this.getPosition(); + return Vector.fromVertice(position).getLength(); + } + isAnimating(): boolean { return this.animating; } diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 3238236..55ee206 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -24,7 +24,7 @@ class Globe { this.renderer = Kernel.renderer = new Renderer(canvas); this.scene = new Scene(); var radio = canvas.width / canvas.height; - this.camera = new Camera(30, radio, 1, Kernel.EARTH_RADIUS * 4); + this.camera = new Camera(30, radio, 1, Kernel.EARTH_RADIUS * 3); this.renderer.setScene(this.scene); this.renderer.setCamera(this.camera); this.setLevel(0); diff --git a/src/world/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts index 459424f..4adf0ab 100644 --- a/src/world/geometries/Atmosphere.ts +++ b/src/world/geometries/Atmosphere.ts @@ -10,7 +10,7 @@ import Matrix = require("../math/Matrix"); class Atmosphere extends Mesh { private readonly segment: number = 360; private readonly radius1: number = Kernel.EARTH_RADIUS; - private readonly radius2: number = Kernel.EARTH_RADIUS * 1.01; + private readonly radius2: number = Kernel.EARTH_RADIUS * 1.2; constructor() { super(); diff --git a/src/world/graphics/Atmosphere.ts b/src/world/graphics/Atmosphere.ts index 7d0522e..e6060ab 100644 --- a/src/world/graphics/Atmosphere.ts +++ b/src/world/graphics/Atmosphere.ts @@ -5,6 +5,7 @@ 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){ @@ -14,12 +15,30 @@ class Atmosphere extends MeshGraphic { static getInstance(): Atmosphere{ var geometry = new AtmosphereGeometry(); var imageUrl = "/WebGlobe/src/world/images/atmosphere64.png"; - var material = new MeshTextureMaterial(imageUrl, false); + var material = new MeshTextureMaterial(imageUrl, true); return new Atmosphere(geometry, material); } onDraw(camera: Camera){ + 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); } } From 98499953105b517b5cd9cbf5f1af4b3d44d13b37 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 2 Dec 2016 10:35:13 +0800 Subject: [PATCH 092/109] fix the flip Y bug for MeshTextureMaterial,#16 --- main.js | 117 +++++++++++---------- src/world/graphics/Atmosphere.ts | 2 +- src/world/materials/MeshTextureMaterial.ts | 5 +- src/world/materials/TileMaterial.ts | 3 +- 4 files changed, 65 insertions(+), 62 deletions(-) diff --git a/main.js b/main.js index feda64a..145b7ff 100644 --- a/main.js +++ b/main.js @@ -1,60 +1,61 @@ window.onload = function() { - 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(); - }); + 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/src/world/graphics/Atmosphere.ts b/src/world/graphics/Atmosphere.ts index e6060ab..671b0af 100644 --- a/src/world/graphics/Atmosphere.ts +++ b/src/world/graphics/Atmosphere.ts @@ -15,7 +15,7 @@ class Atmosphere extends MeshGraphic { static getInstance(): Atmosphere{ var geometry = new AtmosphereGeometry(); var imageUrl = "/WebGlobe/src/world/images/atmosphere64.png"; - var material = new MeshTextureMaterial(imageUrl, true); + var material = new MeshTextureMaterial(imageUrl, false); return new Atmosphere(geometry, material); } diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts index cb52f2b..8ce04d2 100644 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/world/materials/MeshTextureMaterial.ts @@ -13,7 +13,7 @@ class MeshTextureMaterial extends Material { private ready:boolean = false; private deleted: boolean = false; - constructor(imageOrUrl: ImageType = null, public flipY: boolean = true) { + constructor(imageOrUrl: ImageType = null, public flipY: boolean = false) { super(); this.texture = Kernel.gl.createTexture(); if(imageOrUrl){ @@ -72,8 +72,11 @@ class MeshTextureMaterial extends Material { 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); diff --git a/src/world/materials/TileMaterial.ts b/src/world/materials/TileMaterial.ts index 11579e3..0d2d143 100644 --- a/src/world/materials/TileMaterial.ts +++ b/src/world/materials/TileMaterial.ts @@ -8,9 +8,8 @@ class TileMaterial extends MeshTextureMaterial{ level: number; constructor(level: number, imageOrUrl: ImageType){ - super(); + super(imageOrUrl, true); this.level = level >= 0 ? level : 20; - this.setImageOrUrl(imageOrUrl); } onLoad() { From f9f677a27c3025117efc561075ab96904179d7ed Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 2 Dec 2016 10:49:35 +0800 Subject: [PATCH 093/109] enable blend and disable depth test for Atmosphere,#16 --- src/world/graphics/Atmosphere.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/world/graphics/Atmosphere.ts b/src/world/graphics/Atmosphere.ts index 671b0af..f9d95ab 100644 --- a/src/world/graphics/Atmosphere.ts +++ b/src/world/graphics/Atmosphere.ts @@ -20,6 +20,12 @@ class Atmosphere extends MeshGraphic { } 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,使其一直垂直面向摄像机 @@ -40,6 +46,10 @@ class Atmosphere extends MeshGraphic { this.geometry.localScale(sinθ, sinθ, sinθ); super.onDraw(camera); + + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.disable(gl.BLEND); } } From d2516e01040c5e9e853e16f37c8a8391af907d19 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 2 Dec 2016 16:04:22 +0800 Subject: [PATCH 094/109] update atmosphere.png,#16 --- src/world/geometries/Atmosphere.ts | 4 ++-- src/world/graphics/Atmosphere.ts | 2 +- src/world/images/1.png | Bin 0 -> 6427 bytes src/world/images/atmosphere.png | Bin 112776 -> 7291 bytes src/world/images/atmosphere64.png | Bin 2767 -> 0 bytes src/world/images/original.png | Bin 0 -> 112776 bytes 6 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 src/world/images/1.png delete mode 100644 src/world/images/atmosphere64.png create mode 100644 src/world/images/original.png diff --git a/src/world/geometries/Atmosphere.ts b/src/world/geometries/Atmosphere.ts index 4adf0ab..ab1d760 100644 --- a/src/world/geometries/Atmosphere.ts +++ b/src/world/geometries/Atmosphere.ts @@ -9,8 +9,8 @@ import Matrix = require("../math/Matrix"); class Atmosphere extends Mesh { private readonly segment: number = 360; - private readonly radius1: number = Kernel.EARTH_RADIUS; - private readonly radius2: number = Kernel.EARTH_RADIUS * 1.2; + private readonly radius1: number = Kernel.EARTH_RADIUS * 0.99; + private readonly radius2: number = Kernel.EARTH_RADIUS * 1.01; constructor() { super(); diff --git a/src/world/graphics/Atmosphere.ts b/src/world/graphics/Atmosphere.ts index f9d95ab..e70b3d1 100644 --- a/src/world/graphics/Atmosphere.ts +++ b/src/world/graphics/Atmosphere.ts @@ -14,7 +14,7 @@ class Atmosphere extends MeshGraphic { static getInstance(): Atmosphere{ var geometry = new AtmosphereGeometry(); - var imageUrl = "/WebGlobe/src/world/images/atmosphere64.png"; + var imageUrl = "/WebGlobe/src/world/images/atmosphere.png"; var material = new MeshTextureMaterial(imageUrl, false); return new Atmosphere(geometry, material); } diff --git a/src/world/images/1.png b/src/world/images/1.png new file mode 100644 index 0000000000000000000000000000000000000000..8c5c15ed1fa1d4a1910ae661ff42f91350cd2169 GIT binary patch literal 6427 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 literal 0 HcmV?d00001 diff --git a/src/world/images/atmosphere.png b/src/world/images/atmosphere.png index 4f0a404aef2cf914a8a14503fd0b326f7c70a573..f1b1d02df59628cf979a705596d3f5638ebf87f4 100644 GIT binary patch literal 7291 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 literal 112776 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

      TQI2ry3u*GGJ_P}r> z9`1b24utk=Wy_o^22djAnGlfW%qcLM0~H8MgzmPlKCBxE}QR{U+tt& z%?_HwJ6POQNiDWm&kKp$RPl*Rr&7(P)<7p?B=(vs)g&Aw2pkv=x(DsH0rKIa_w-R9 zRaP*{jrRFG{+IvqfBg@ND8(NC)>U(AZ$A+fZ0&WG&xz*&=fO}8Q(#?W7c;-{B{E{C zwvpTcU{GEfpOH@&-#tZ459PiOT{f$RweR%e8!;8OJT9tmy+T-3H)E^i4gCL))cDN) zt}OW1|DC8erY(VeZGLMg2M}0Gen&06JnD08efH>L3a^T78e&h-m!ca9%2xn71DNfhG4lS(q zmn~m?xYsz|(H|#$B6I;HK8tvWFdHmjn@v+Bewcb2g~+~isrn5Vf--rC$n!ZbIv$hn z%zRsBiGTT-$NVj?I}e>#zR7IpUL^cP8*?`oL4I_r)!D=u#7 z8{|omRL7P6gZ-t`+_S~e#6#dkAsN_AljfS8Ni?o@iv-C82)|?e(E>&cs4?qZzgF~zX3Bp$O5>=&14eshQephfh2hVgo7r~BNeAu}eFEY|HtwY4=4uQrG0fi7LN*ibt zGHL{g?njWbF|zmQw%eJf*9yewj~OZUFz`1Tjhn9H5_x zn>DGE%AoP17mSYBf+m(o?>~ky5fM!&J%Cu#QADE}FHNNd&X}D%o1jI|ZUR(tt~1?1 zWYf4clDEp0E?rOauAs8XLDvP--$xtUbQ@==Y+Guz@jlmA84U5Q+9yPY9hFErSL7L<#)XKc;U6C z&EVb~e$COiLoyWEB2El4SZR6JgbB;#@J7JYy`tp+HsQM`*b}WOIFG3p2E+5YD!D11 zrWfz_2P(&t&LYFNvDfE2&foFWM49^vFM^9%b^v64U96DSJqNKP_v;a|w5Jh0B>ZB{ zZ}Xk_9o|E{Q{=;3A#uSKP@7#MwZx2u6M$st8P>LoTtE@*OwF_%Z(QepvSHM(4qunrolMIla;^Jnf-{YXwLzcapN&Qprh~r} z9P)QN-GC`T-ThsaI~tkE&TNF?bfqWKWhhb?yBW9i90X@!Yd?t|t`7lsA!B)%$G0W2 zGqK2vQyCH;^&S@b9z-?8Y5*85p#zUEkfMh7*#&GL0~ii~?fMy_uJBST3r z35xR{@1B1KA`$mvuMuY0;jI(&1U;t|Jb5%hvC?7*UigVW0#I1-SYGq^;6cG7d;ZbI zp*0Dj8v|A;qcsTE5OCZO(Et_TjqhjOjZ`7UY*XIA+sW1&WIki7dE5&^rPf%%0hat~ z)c;PUDRh(7bcr{jo3PD(r1mXJgn)T%n-HD8Sl>67*y92O(Nz+FU{|HOINl#4 zi@?USI}M48yhC_UM}~CyTRiTj?rLOh7}?K}y&XNI<@cJpZHBrQqx%Zp-O)e%@$y!6 zM801!W@G!tt%jBAb*NtJE?V8f=P*4R_4&eq72fRe=i7r^e9x8dH+i?G6*9mA9-)o6 z`=&&xxTHb6>GyXKlb+k>o10X)vV`1QSQa?zBEzX>Q$1*dWiC(2W8Kby zHrUDwE>>yJ6Kn1Bc3#i4@SrJii<9NnktlNZLzWOFtEWbvPWUSX9=;R#s{5JyJHf9g z0ln@k9?#?$I9W5#zqYR?8QFxyE zi=7>oCSQt_#m76SJQcRLdUbY!9d}%Mg&-oRHF?6D%1Szmcx2uMT&Y$eZ9zTcYvOzn zkgi}H`E&!-A05bLlo@$MKl=eE{+~y4hFClg?{E-=Dt;xwql> zurr`}NIPK@!x;%sifvfax6}3S*ia)BZ7=zqXnt>lavX%hp9n~%pKUjOQ+7k?v`n46 z<$7#YN>7Z7cKnDF!JB4tFVE{T?e1t4s9m-Erg4bpjX|R{51Rfg&ek83^8?)` z%1BS67WknS`gn~OwaZ!df|%B5H0z@uz@?>@V#7q(tv4r%TZ-W>j3`uFlakH^#X1+*YmCiC&L0rqnPUYpaI^uKe0bcR6)O6lO3@poR6 z4ssH&L7z?udlV1xMrwT=LrM%ls~ojH=_> z4Z-c?mf7izBW12r+|b!NZ~QFUe`cWKzcBWh7sC@%mNKlc>A?o${qomY05`v#weFn=t zhQ}ovwRDU)3%(Z~=8Ti+60#J#%i#8T0sHVc2Hz~ulsnY*;&if3cDbo2dG)^e zLrO2W7^>Hb@u7q-iK>eOxfM2BJ`4SoQhWFG{?wo^S}S|9Kj!*72hi&pF3aX^F5Z(q z_*vKF#p*d#0R&=^@W0vcz%8^4qfx<>g`3y zLL4`q@1kp&`YT60g08odjt4&%TiphWo50}|k?TI{4OBk8XFKl54s{!b^~i4Anlk3m zdn5jR*iTs!W~&e8JC+&r1vZyX0toEN8OIC8K) zLo~drA8hLjhl_zMpU^>7jrW=}%+LxYon<=hhthS|G#sdB$MJG5?P})h;E>vr$5a{W8lKx_`yleNGYe&ViR$bEncXIN5~S{ep@}5h?FqZ@>;k?W zKhC-U;PmECJ>yRzL_2)welTe!SE3yZcWt7s6aQvxGKtfT-Cj&`9%t^u?)3XlCUEHK zCC|6jH0}|jA?0l^`&))okaYc^RQ;aMEy0!VwOlEPNJ@X0n!u1W-q=;*h6p`CtSJ5O zLJ>FkQK)CSVhNY$?&1R{Zb9~`^BYGC`<7ewpNkZ)$`p#%D11qQM!c^0eSj5@9*0HNxA4F>^!vU;OSOmEEAD%_ zOOj6n<$nGCedSW|bDOOK?RsQea{wBo}bv)3S9j4c1? zA!3St0Tqm+7sjp17T^8~N#G_JT^e6ENghXbZIPXhuRouQuKD(INcY9k%5*3xiyU)*peSK zwYphX+El@@aM7KtqU65n+FN~h4CN-w$7M)eY0E2#X%pV-f3i@V(21Qz<0;es-|~O| zBp^HqR!+w9^jy1uZHEYTf#ztdcyRCkme1}4P5Y}15~1q;^VP&+gD)la{rvgz)#ut^4l^&VM0G3@%-33yjy#*uc0QnTv(As z-nyO&6L@c4d#OpHj zy?9L1nqU3m=2!{MbA-6o6VsMA^>lyn-L7AI3V_)4Zf!Vd5Xs(MjyLBSbw%+#U}ro* z92Dyrq@y0%D{NdJo6q%0QSY#kH&66E^k&7D_bOpZ=aFdidB_r_nl&g-!qRS31#$+% zzfbu3MX-VqXzJpT#Z##IYMxvOZ1qUqft-j}ncUXgI@?G#)}(9eyZ3%(UK139^7805 zNr=En4!P6|aq*e1E}H6l$h)Sn%?!sv>!lF6VaZV0J(m=|xE$IWn@b!=ot~|ZM2KNF zeZCmVF=&Dmd0yBkg&QHaVts8j5T)I5bu59ds|#WkVQjK!7EXfjX1vX$heU~knjS^4 zhk3`_6ywuvc*xdIE;%dW*%4}*V+BU-^3S*AYF{HisU7b&`wmsO4rxcb8ffcS;+l%}ni;I-zC;V=2nDWwsipix>+E#078d z);q&{$T-!U*z5Oa}_t=cKDfR4=wJi=u0nkakbz9SHHnsdvD4>DX8e{)}=|xr^3d%d;lo?!(hu-@Thfr(wHbwjW zZtU7c1Ly^tn!u?=6`Fio>dXW4_{9Z8;QqS|3k|xr2Pkp;TAUDOy{JzD^*yLs>|Kv| zUUO7yE?{=XLHNPI)Y~uHh0z3P*o{jQARQ-jyZJHwx|uIy@~j+9cSJ5R|ACLI001BW zNklG-?;PrJ8<}z)r3G4J7tdMlQNW z&*hG!uIBczcMB<$Ti=W<%es<-QILt@g^!ufMU|bw6R10u(celfBks4q_j-1IP;>js_jhJSZ&>!-wY_1$@A{V~*xUd9JYA3@ zUr!Uo{UEs6tp=*)xXradRQ6a-cO+KQy>^{f#zC2H#sLS~PKWR1$s_Ne_RhGn#7$GB z(3=FHndd2~FnXq>`7vInn+$fxVLO=ng|zJbSpRKN+I{3RKqc1@oHkhxrkfdr%vv&7 zvxvHCJA7l3@KzM)1UoymqI|PZ+5lD6&CT0-&s0CUHf4gY=H!PZkP}GpWD)r%ZE6!i z%MUiY9o&D7<(A$Pd-6Rrrhk82QrVG~PZF^so9e)FZJ+7qbxm)-fM8f;|FWQy;#`4^Hw%pm zcC#GRt3u#MI^sL&$>v396ScwT+ozh+sEGYNwwC2RJJe?@N8LRB6)b&djt}lXbiTjGyaSH?^NV<8OVi96z0# z(vr;6;X2s`>s=7{Mt$!Zu49tWwHIrrz1suEk6Ji|4|w!YvqUDWReWdk|B2g=6KNKbLKYp3tqz*IlAv z%#2+$!rKhRB4N$h-x#oYB~VF}wB5Zv{28M8$7k`0)AP^}%YY_I%cj3Y?44=$46WHbjrE`v1c9vZ_-k zj>F2h5c|;C_@faKNUT91rK!lND{|-tkfmHV){E5lvjUEV`jyKP^RHdmcE0V_2hX;>XRZw@?TWVJeI1R5)T|8o^OP)jtR2j zT6f)0r($RPyxz*BtBtuE&)vA4O7Nbdoqv#i$ADnU#1$QZABM*a=o`^|sRSuAK@_jS zy+wpX@#{b1b~lL=W8&8QOiNgf4kANn>SjlxH>|%qYU5CnnhjtW_n)AN1ag03XlM$f zcTwa~`dRM$4eJHZp%@7wCCMU_Kp#XghD~KUs%e?PqFEcw8^DB(4zE*qXeo`*F}sDu z8~jbN*A&Ao-jllS^b%rt!wA1D4ltHmGJQgE@NNO=K7a-{?jRy1{6AvJ5X?XuFxO31 zc5yGBhw#;WP`O8Yi2Ln7bNyp%7UuFD_NH!{+dL@U zpCw6Ka4sFv*`MxftKNnxttyo;GjfG3I(CcSMp3);?a*msUX?NZwk`Nr(<3YvYu{y* z6=K0j&4)G!pxHPY=mVmasugQ!YCV|rcT~UWAjdk9088f(*OEkC-bB`*;4L&8tF)k3 zpXriDXf#U-lbk?tJ8RF${uK@6uO1vNv~jOh5n^%uoMamW_GlLxsDHArmPlWRx(%pTX9r&ONCv2ALxhOL zpB;B&=Yw&#(RKoTzB1Oz+4)5P`22%f&tGatcDUf~n7q3h(geRuR+-bWcVwCq2fXnn6|&;e_;lHnqj_5!V8 zvvjg74~C~*V|uqX2}!2Jt2K$C=&BRK*UrRL5Cra>>tZfxxd_hPDcBFsr65dU-AVy_{9Wx#KVG+7 zxd0AvnGaPi7lFJaaC=t$bFZdyMr`0>9ZvL}Vra)LqR`jlBGL8JTK|dCEA(@g^?f_w zAAk^Y1-Xrd_D8 zwzX@6(pWn&VKD6hfm+|)l4JV72b<5pb}W9gUT4mSzgaROo?jj1tmZRFSHgqpnpWQ> zv|)mJ(kSD!^J%ZB1diVg<#&;5q&crb11!{ekCtWN;&?s)7M;SM69hh?0ZQ4AuM^Z= z;_|+0x5WLs5J*XBGze**hn?BXhYB{ZT{abWL-hnj-5}LVB&_ z_oN`d*1zI7cn5Sz3rbKuB`xauRd6n1b%i$+D06Gw6URL31HLOcZwjV*H>i#@`Lj7m ztJpzi#zo$nnSFj$Z`^*?9tbDPxBIC9USIobU#32RSV1P&QNpP7u#>lSlr&1xNAVoE z4Y^~Tab070?7021J^a`B7fE6%*4vgpw*_x*kK-WDW!WqIIS-#VWw12vX_aX_xud7F z^|0&maL6l}R{xDY+vH6hk7Yp~8Vm`LTHAeW_uMoh7x8cn(PP_Hq$?EYqN<#do_4xN zkSDiutt|SG4*@RUZyAel?df@hXHoprU7)b!$sX18%6I5*9##16u?pjun?8R3@d|Ug z*NzwynFA2G|5E`Lq3dhh~hs z(43zYWFmK2-_L*&(s$#Aa)$1R^YO5euI?H}(Ygm_caKa|g{VAxZYpv%ZnhPehT|d7 zXW(iqJXAJG@Vx0c`$|qbYfa)m|DXTu{~h44od^Bo-)*~v^WgZl$&Ag;6zcx}`rmnG z?|mKWU<}p-c8VRn$st{2v0l8m^WRXelYw>rgVv&U{r>lOdYx6x?l~y7EsCQzOH@Pn zi~Ear#pF1fDA8DA2lty$ymNms;Ipt%`JB(k$OJI=xf_46&G+wHJ)RT$JZ2fX+@cxc z-CC)vx5azA6++y64gw;t78Cn*a91(vl>Vv&vy~2yViQgmuOzFtJPpsPh($@+gp-mD zcGX^06GI$+z6iZlm;LwJbGHBcG#f3f7k#-lu>UUFJ{4h^ZmQc5Pb@9}RfBpesk`en zok8qbfCCNSBO*o_0?gOYySDe`5!B+K36QOU`=`vv4oXh^WL$xh&3H}MrZS~Ux&*n~ zX=CXfM318d2ZxyuhNqami+2l(g8tQgeVN!wH#N6<-E$l0-XE!v&^)6kT68HyNWk?& zrr0}4$#;Rr#+ADr4%fJ?7n16HI9UtX5`A(zg|W4YMi95Ph;%Sj-TcA2drx&ge@Ib`{|o8 zO3#MYOXa*+*v!qM;g=|{51I{ahH!$ub?f$|Iyce1H~WC5lUv_zbr1O1DTGa++8C5b zef4HTP=W)sycDXtk|Mf_cV^HlxGDKkl?%HZt8Dr0cT0Ie#{4d#7*WnjADxq3Wxifr zl3D@~9z@O(VU3Szw!$PdBC!(%_R*F}rpCR>%B1>XCGKagg}N~X35}PENq~WApNVYVqebF0$Z4i!%?O`rN zjEV~#C6*i{sURq6x?BNb#UQ%Gfi3%+4?!}+?@F99C5AquYRWFUNl2j(s9^sM1DWt5 zi#-!u4~cIXDV*PrBookb>ug@PkOF>$$EU>0x^h&l_JEWZ5ylR!)io=;vz0Y~H_WlCAe0vrgf$L)w1-{+M{X4j{^% zeJQ#|O%^p<|ML0Rt2~Yi{r9h25%xI8RLg6gk%zyPpJOJIL8-@U+?(S+ zW3xJP%HQRD5XlKnrsGoZuJ=m88P;)On_BCy6UL&NcvKRXE_>nlPM5GmwEMbv6coRk z7s~D_-2@Y%m5cmPjaBz|E;0QYF)it~X`zqb%6V%b>nA0G<(Ai0UpA#gM*NG^s#&8c zuiqnxz!qY=;lLL=u7GcXX@~^GY69~Fk6k%=r#&;R^_}U`Uks4`qQi5dQS%i764-AM z8xY1XM>Rq*;wDnj2`8SYsSF!ZJ5yf=!VFVxodaD9g6|ohad|OwwUiFZ4pY!_1*lHm z8%c^lZIHHm^>*rLp>>HQs%{q}KA#Wg_pH1rJ#Ab-=HJ`(atEOHR@PyAb3O9Azq(@tMellSslbNr+{|i zN;jVk-Ov*Yg=MmJ>X3;3K@FVPlp5uGjCqc+3YZJ;YN>R^WujYxyMd})LG{fdlMMdG zg&FXRvQAgv16T-s#w%d~Rdqd!r>z=C+vB_k?$c4Qp>Pz7l0uS8EfOLx(Xf zUbjQ7wqg)Jc)F@e<&X4mDsYVKdOIkoD2@9o!q#nY(Pb*{4 zHC-|-N1hLsySb5jzHcv*ygxW8RCTr1uKvrAUex-h`^B(u1eZrisJ#~yObd$g1K2s) zb*(jn-vTJBZBM~k`&}(v5um++R~af)*X!c`J6~?~EW_e0?`LN}+Kg)gO4wIT4=aF_?%B*nwu}AC!8mUG~K*5C*5lV zF(nJ&3QfXCf&eUVmPV$GWP^ADF z?vf!MU-!geclz#wLdM(8rNr+oKWb~g5?rv}y|2?30(*bw`=L*Kt{xWzjPP2NYIGup z1QJUjVmNWfYWD|Rt0wjFV~wirH4JWfVWBZ|M?i#yexD#50S$e?bE92t`V2*5Wp!K=S|RltPQuH+IS==1rT*<*dG zWuHLoC%xitA@rTt%&8{D?z}rwu+zHvu2j z6O=lLE~*Yq=4;_t?uaIUWc+i_2%G~oE9y{{Kk*B@D`k<1nmuW?fatft*;1wiy&ZjOeDp-znd@ zu5HV|T}?o7HNnP-RPm{tD_%bQ16}NK@=YRLW_zbbHJ;rB-DNtL^$gZsMs)h3*^AGd zEI^Y=e=iClj%hIwZYRqp|)YDui9$BIj4AhD0? zGhU+}bZT$Le%n_XD8S_RPK`mTG9W8NZ59bkW)>ZbepfFty--hL8?_c>GMkbMW)(FZ zdwAh5k-tlv1GAwAcA0Tgzf+qAhK&y58DOeg?y}~;7=-K~&lCGyv%OEiUlEt<1rj)A zDzCC6=m-)bGT|uE8`T{*O<>~Rdt%O_^%c0OU9`q#eR~gU*3+j3W68WojJ_Nck)pi( z<@YJVK=Y#Z%qlpQj#z-@K7!Xb4(V?Y@C!z0(Pi&dp^b5&4O!98k_d@ADgU8Uu+86h z11^@gd=})p3nJIr;@$zLW5!zRQ=T{slS=|r-`#|f2%2cvjI0!46 zqWSA^5!s17oGI^}{Tqi1>zjoaWYVhWN6M;|-J+d{N{jH6?R$StYIH=iODLJ?%^@4x zc9DD0&rUT|<)(cK$=CbZRpub=eDyVY$}FO4xNn|wrj@O$M9IHMI*8^v9zO_O3dz|C zWCjs4j_7x>G{eo@uWea}Gog&!375!#dJ%ilDr@r2Y9d-T%anSO$h^@+FB+Bg9-MqQ zVxQcTEFiSckv{s8FN~6h-z@EVHr;$3U^ePvRXz`c_SvW(;)pkCqgZ;!F(B2TpMmMZe z4?!_=DP*^%KlBpE$(TCOvGl<(Nx_b5ybbkX7V+r&O{r#=A}@F?7$&3)jG7%F5RTTQ zTnjVUOlnckZE#G3LUD%fjMOuI<>9aey8Z*g_pU2K7`G`!3toql%;An^) zr$|*RZM~7YE*VnCM1weYW2-w}<0N^B4Z?@Rk_5@GNwSnHq;78^zQ?U}06L&OFjA$^0^e@<5|h+pL+` zG`Ml#SrNSx!@RE4tlm2|F`9%Go;E-Xo9?McxDN}>U*LM%)CW%{_in+q&BhmZ`AKFH z2ld@)ye`{oJa-$>q@TTO(>XzWPR4a(GIIPs9Mr&f9uC+9Cc7>R1s@86rX2f;JZ6JoW}vR&A+TL?JZSlw4;w0v5hVhJ@F|FyujXv?c@K_3=mH{W3Dwbe z1qAA_YvCr?c2@3e$9|aZCv%;CR`SzCQpPsmy~Om1R@Gc5TF*N9Jd|+~Wi0Pm6of)#F2lO*v+g;ZpC}(_+!IH%=-U{yT!!BY(m`yE;UWzz~noQYhgBam%*n|O?m*Qt{&ytJ$j2)Tv z;^snCc#U=hUE=)gA^rg*F$h4WK`3BHS!#=_Dp(#E6pA<>001BWNkl~)vqC}8cUe$+j=abTbksW)ztC=`CdYAYXk8yde`?zH$OoKjaz(l}hG?vt z9y=dA_y9>jw!hkY9n_=>7=4ns$3-v>=9k~HLaORLmf_{f2w^59hf&+wEwtPacR6cQ z=Bxvu3Sr#3!#zqH6c*~=m3WC2mn?Cray`~#LaGLCc zUbAYxXIR1-qmK9jIyLhN!xGsG3xXKTw2PP32%scn>o|V>xKfuW)nWC)J<+c!H&WGM z@uyUDi>AHVby+WYNW^ROl`lJ$IE_3jJYE-`4a9@)g-gyD9WPxwd1l=Ie)jcGScIKS z*ihKb2>W~7oj_4!F&&+}v#aN{5S_2(ytIU1$#QZJ^ZiUXi(;0(?jK&nD<7k*1!-j# zpMn5YC(bE3hX37=SAiLlAA4NfUfWngJ+AKWn|^JZy0^q;!!~meG29V5uqwUZz(xQ-VZjYAx8U1Bz+K6TJGQZ|98S1lG6BEm(%`%K&mf4}eLwsb*id`PA3Uut!CzQK#eyQS~1 zjh7V_81A9|E##?uakmzAH3D@x=ktig9#24zfYvRSA3Xbv;CiJ@ntD0!?>?{3hh#F< z&)DQ4HF`VCLU#4));+}&Ydqz~5H2)eo@8PSL+`dF0!I($WcG3AIGEjV#CiDOeo*V_ zyp*^8->KJTUw7X^T@bsmNYQru0=479`>ch>@0E9VlNF_w8J}TA;r0_@o&?iaJuXz* zFE=u*9`RJWy%W@+iYJ0NbYm%l&}{qJ1~$>ri@PZ&eFdpo4mpP*P`&lKyKsdZo~g|V zxaKC(^R#N64GNNq7K##5%PjPazO&{DJwj9eRvIN8AID`qTnV3Ptrm>6Y{0DJQ=w6* z1bVJg-b=I#L@s49G0__2OQC3JOS-)rw6{1|SEj~jajy!H!rAD6B;J){!)cw0hBpO@J^C)F~#cA0#*=fp(&v&>VqN-a~(1&;B!!eMz#{6*6xjxPh-vII3!zQ z{~S+hX_B?pY`CYLCWUt})P0m}w9`2Gp_JgQGm^)Z-gD`aG-RZoa)~p*;U%oe; zjP{N*HFZ^~HToKo6xf_(oG3>gb(%ECKCQ=}?o}sAfNh?cdhSY!{u;(=OhT+DpO^jo zlh@@^>x9_j1}}DR62ZjuwZ?zFn@qzC>>$P|?K5dTWB8PrskUE0e260?^nTjD^NY*8 zM)tN$sR^7~*O}#rS-zNMA+vO3mWp&KmATHpYHRBlo$y9}t{!XtK?ZZ$Slxr-L6Bcw z@|bzN!oPPl6}m4KEb7IE$7`KC78t99=AH_cUqUyw`_a-$neCyo-YSDs7a0%c)wKA) zM1_A50OPyssiME{1<5e6Caj(@3iz3IZDy*$Yz}5!b?^&jP+nY}90T>HtQO~8aB^xE zbCPhKY_i9Q+GgXMf`r{3Otb<8rnUiBe8p6WQY0LzTUB-|ykOp|ZN;yqY7#wP3tjAXOqL)u`MHB-n{eWH|5KqEwrG&u(E_+S7+?+DYr0 zfH}J|lwMf#=6_|ll(dd=@F>`dZj~R_$U8uRRwi`5y~aJ*Rfn8gqgk9JWhj~6W~pJ9tYsr5o&O< zrR^LIoAzc|5k0jsUS>d1QD?ntNftD=&ioo_mf;Sv$-@0Rh;`qQRhp7X5ZL^kMIA^5 z(3Tq0T9kr9Uy%2F{`M%6Wpkq1W*E&HI;AC+2VFOdWJnZEmYh2ooL>6Wxge5Q7mGwe zZiEpK*DPHm$#G!-xrrHjPMe6V8`)6sqSGM9D(I42rmHGWx816!3gb-6fXxf04=BE@ zez=ATRR9Nt98E!~i$$=D2-J;SqPPC)1K6^TUN#yTB^2Pwn@i_=apJ>($&;ZYdRjAP zsF~%fN{Waa;5ie3rFIeSXYpI)&;rO6UJ{4MOta>gJMF>&eex)X+^8_$OYP0+Bj1}S zT^|8Ra}Vl^nJ5JX$gOLu>ek^t?Uu6-02@fnc~RgI?=wm5JB>@!FwCu=GdF@cjgm^A zRsyGz@Y)@&cn$8oy?w+*^Vp5$=hVlww(xaLbi6or_A!97P#-e9t7KkFNf7kA3R~%H zGiJG54sdZyh)wtV`S=-O)`KRqbe-jSA>@=m6xaisV2+pQ6iBD%&9+(GVSUsZ75kJ} z44V33+gUKdh2EI!oJ6}j@Sw|fFrbt^|GoXWVaiXa0>p;tJDcTdQZ>BXCdG5w1z|!} zF5CAtpIRBe$$)IAQSPAQsor{W{ci6pTX!gs(ichfa){_q?Y~z0uTA`}eJ&W(1PSz$ zHR9RpravZhztESv8?W0=ej(vs~_bU5~yrS}61c~S&Y(}J=pK}gFtXp1b= zGL$xGxjXn$u65CdzP@Ub%))H-Vtp4Wld@Wp&Zqi zc6F+5S5kt})8c^5am{)mB6Q;O6$DweL5e&XFqxrrt5VmoQ)##-h$vSl6y7fQ>X;{g zJ9UQh&E{E-P1oz)c!=k*tNV=`;hmC^uf;n_*v;k{yexdEi((k}@@#(BdkOE2Q)Nca zbNRXdYf~fM5ubJwQu&Ag+M~q6{hbC%>{S9fj#?wQ^<5x5Zij)B4|Q)eK__fhaJ1ob z(>gfKPrTeiP7;XW{g@Q@B=XauJ2}vo=o^Pc({qYVXpHbSgid%(#<*cdy2~Dn7wlta z%*APf+2(=o7JC1~LU`ccJxbxWB*PO%%3%sSFN_u1ad!#%6!<1Y+%2p4hOU}y;D+?N z0%5D7D5CZfAOx5R%{g!!ELq||`X8Q&ZmUB*M_Z}Gn4xyagf|<78{xS>6DorBvj4=3 z%$dJ;pW_lKN#~}P-qm<5%8x@1&e{yyU}*Dx<2L6K3z4u6F22<);M6W2Ccg{vi@Q*&OvjC-kf2M#^*tqS zW=lDeWdkI)0du{(pUM z{@RK0CPD13&zrJc_e`$Md)1b)K1G$I*2j~O1MN&{75Dn)Bxahgf=)>*BmU(eTl8;j z=h+SKU?Iw!@s8vWLQpx46$&WmO8~)o)FPIgpnY`THPh_*fCxYdqz#cbniquOMs(_f zb^w)$$tvNm0rrCM5Qxrz=*lMo?n0-Kr^aGp3}fa{bT2tJor22#Ys`dr+&Zbd?Iyih zsHbCMlf|4YlsiMEsdVzz{qQZ~wp+ygU{kY$vF+w?|I87I)hUdR{ux)$q9fH-i{20H z>tK(s1AitvL7*8ygmYReMKgya%s{9OsrfkUDEOB>G9Vd=4$#r9QK$XC)#1A3y z6s@*_a<_=-^}KbjSS{9>R9-u|oVs<&&R6br4wC%K0qEW;l_Dw&(&vJk{*`X^^bS8g zeDl#fc1*`2JV_|Ioh{hVjhI~{>GW?_uZn%dW_dAqrcde==aDTD= zjm?t#~zvGm#6vF>SX)z4mxc04`1Y-QoUKnJPoq;3w? z!yILyh%f#^-6&Fb)vXd28<6BGdyU)RZ~hx6(PqkAhN{cp0=4rfGb;jSw_J<&bghwf z6+3R?Jug_t(y`WkUWsY7bS~o9c{*nUsXXZD8D|AlFDn`&&n^THt=DelO-i%op%t+T z>uT`ABWQp?L0UTc07nbo-l;IP8EpxFPg?eCLC5osv8JuV`AtJWdw!6I7o7*-T3X0@ z$y#vX4IcuL6k--xzmJk|s%SpC%Sr6HOA-SJ>hV1hB_RPn7TC$F4!4Jae5-fWuNxIE8K6O@Okc62DJbdtDJIgSoSb!nE(w5NW~Tj@K76r z);q}4r*^%_nogRJrCSXOdR!Yj@eqz+RiKB3?+zo?Su`5YJsmxjmjF;ODxUREsbafzMt!lk67b@`;4 z*>A@T(6-1<#&T{%6I%CSF~W_*Lbb^fzs0CKC5wqOKyD$ptB=|fUIPjpn66SsZO&O#so$xcdhB5<&8u(CRTR}`&%(Uj?iCnv#Jj>x8T%dbzN-e%E zlQ44D69!M_sA^qSoQ6w8`8g`Dg73R_=&6jUQW+nTc+6D~Pi z%1^-0R83$9E0qW(879)%`dK}j)qY9_vq`@|5!tSt=^~g-X<(nMnp-V&EhGRUY|a}R z=E%fP-amDp)1t1GZp+GA9*gT}`xy(8mt*+u5>Vj9km)9hn#3a7jbvcTb)paqrA+V9 zPWwQ4jaW!or)L#yAuW~H`W5xf`t6%ae4~VJ^SSWLntSbGHcoZp|%=1u$w)jope*>nJ3>gO;0kOs~wI_x*o1$ zW~<_0lIq}PXT&2UdBfk`l;5({+_VGsHyV#uaNRm@3f@P?GDDsEkJY$b&GnpSxb1?W z_$@e}SKD)=-z?1}axxiNJ%kDuXpdM>lJhmHM^P-b<)8#*Pjpcm;b-ZXR#1@PpSriE zh3)>XX{s{&6-k5+eH^=BVo|xSGdP}u~)Mkm}G9J+y+kj5HY$VIgpoK$HcNWilE7qLBR{c>7CQaT zQ`vO_G(0E}hX?|-OsjoO<|TaW+n1Kq4BSyx%HKg__|ta$cvH~;P2w(}5CNo%U{ZEY zvXnaJst5-qMM_;fkHmAch8xEo6QW$40#N_t-3i8%6YGV!`AzZX2$JWhbufK}A@Qkk zFBo^Fgv>J#-8Wq1A{+>0UcLX0f8ED<7;XA_JR>!!2|ghFblJK(%`+uh(HkT8B|698vLo zkuPzRFO#Pjo?JnCYTXo)1bSS#`W?V_@cd!4z$YwXc*M7MZkoKFLsgf2abB2GEM+Q7MJ4J3v6%&EB;RNk( z#II43_;C?LWHYY|t3jLK-Xxsv*9A~34t`Y0N2uSaCN5B8*qZT|I2Pzw%|^B@OO$0> z-iO(a{m%F7h9iY5-&UjY_*KXWM|bknNY!}7Ug9u2r+S{Es$Yyj5;=>*{Wy^)64(x~ zQpcP=cj~8}tj37vue_|g+HL2FZnjq{yVABH&T+eQ(k_g_by8(k0jVlZp9npD-d9F? ztpns13&eef7AHkibt9I!x7i(2Di--PKl`fKcQ4MQ@?6?AoHA#En^-IZ4`NWWPWInO z_>D_4S?+2GV+V1)Aru0w)&;bS-q?58hTo1z&SJsc(JQKI6fht+d~;Nqc2$JE9Fb7rdo+k=TZf7!vvxV!5*u5pM=wskuk=_wp6t`i=5}wE| z+?Fu)j5VQ3(GSnzEd>JoF$*lVdUGi{HeD=aU(xbB@IuGDIp&FW19s;?&5U5erty9K zO)ENe$Ea8<;v#i&4cN@Rf`JB%(Q&5srrpbi@1X`gqXHayltkVyG>@Ykdu%pr+_;(# zik?q7R_!yxti3PsWbtCE*4VNx0XZmr3J}=LD`dRBd8aCQS}Sg-BuEqQc8S?O8O_~X z?ffaKsEUg20>?HQYa&PjqLF9vun-7N87XQd6nh3nUIH2Mc`4+5dS^JfR`0Z&iKOA2 zwVW{D)ut^pp%0XfWDQ3JWzOwf2dw;7GpHj4i-{hI=dQ@~0zURed#Dtr)ytUM9@nuv z_pw@uj5k^P;IgP)# z9=C7y>FTC;|GQ{IzWxy4+d&|7k{J9eB~*v?XiHAstY>CT5-@sZOo7wz*;D=Zx54S; zmMFh7NKE3z^V*A_&CmBlCfM5`WosCQ12VSqU*H^~>>=IrA~2Og^K${6BUG9Dv-52(B$EyC9+DFccLG39bpP; zB+xWJP0q1qqT;%X>gypLoowzPi9|+k2%jBE(*1KdrQ9@9af@7=P76nA%AXrj}Ip6ZyE9w?=6bm=O7KUytOu^Y3scXwx zD}7M)F>-Q2mIckr+RfaKFLr*>9@$BL@evmU9i~J#oF8A-Eiu+3uPoSVMf` ze=?(NV;98kV3jHw&yyCFbbiukZrpUzcp%%B-kzed;I`ZmSRjrpM-kZS2;&CQw?<&! z!`zjepxyk=NW?VO66ZE0&!nb|5O~O9y_UZ{5($52396Xjkig9{n=^1djY_(n z+4fL1l5QvW4m8f{lZfT1ut3GV4zcta%jM1lJ!CtLRH}ATgsi3gsQ#xF^Qw{^;IA{! z$?Kvwo}Z7y-Wpofum)h=fAw*?QVL(E>g1?_5U{iCt?)3QOCEe)0ODLc_ECB9BB&j- zS27EKGcHm-QS6c+oE_n9fr7V!xIUEesap^nTdrV<;Tii`)5htt)OB-g(#skWK?FL# zmlg%yv5CqbY5JbngBt7aG$hJOQ#4#RHD-=jl+xd)l^xwNp%+72PyYJiL%xvxi-=J! zRJDZ7a-Q+$sxhvwPeWT(Qq`M9Pje0GLg@6jJm1i0#59g$<=NEhi$7l>TaW}S%&KJN z0!>oemGYi6*AWZy^h}hOx76Vw9^+IS+w&@Av-wVUw;Ozcur^XlP!em z=W?x$b!9y%0LB$9c6P-?tKwf|D-OTiB2!880@w1L7?c5-WR4B^^CsAqPpg865pOqF z_ot!km$*e(Atu322o`(HSV@?3d$osf35fpHF~Q3TY34cs4+-$3UbM>yPDKj*aFMai zVsQ$GOpC`g)SynSDsd2qU5B-*-J)XuV8NN8PF-75`i7<0j%<;WRmsNM`dsr1;EYq~ zvcwbW=LtN~;zHtF_c0~O6T$E6FZEfbqTl1@K5wA+!skoXg{*pS#&>?s!rLdeK0V*_ zPOVYR)!U}`2UN90zo~A?7Qg21m(4DPklFJO^;)2$i!|Z6R(r8-km}I0zP15DbN<(| zxyca5eYd7n@u+$o6M6YQ`(wX+>y!j@Ay}0$>l7iHl=0~kPiaa{dc5Z}=wcWlfy~kL zMn|=J0+?}lz;}{f34a<4Xm>72SGM`C?tbxa8Bx;7>6EMjmlbUckqFbheKs$?*;y;p z&YIMk#kX4=%x3kb9ZTL17w27o^EOgHA+5cu5uOK4JFW9hGk)jIZl)z*_;u!4b{zzB z+rSZc^?PrerccHlQOfRan^K#x&7BNgmYSTTjk1d2nOuQ!pvaUS7~ds0uquI^Rkt(= za~pK7QuJNPatAOG(^^t3fDVd33H*a$nJ#|8uxbBmL zNCca(VU11!25562?UHUi8yM|*+(K`@k}FpTAss8#`hD<(Aw&(`yF{Wp*dy4Z#~Fwj-l#|!m5P#MQbSzn zCU=nmwcej(L^hg-R>ncD5icg)$;5K;ESf+sjiV~TbxEa)c)K zSmGxDMRHelFicXZUAGDj*3jm7+%?dS!Diu5?`@gi!`>BMG%lN08 z=1HKrJ8pxdqANj}aKXi90hj_`9F)y=*3{2&J|TL)qr$)lcV9d;j;6m0WC$XqU&i92 zJ$yE=2}F?3y`>@S=J4SzV?5mJUzsI~X2!|$J4WR8tz+EZTM>OUyWUqSy+r3>g$oK4(KqzspK!<>kTHOV#O7K;IFzfEHu?P~P7gn#8YS{TaQQ6xM7ZB&`w8n+ba!orm5&-Eq z*o4bhg>={N#WAj&0gNRJn_zbVT}xVK9A9J-jy1cptDSfk&Tb-S-@MqY z$;vv|*ND%lHm~%x+3QV|Kuer2qgR07*naRMD(wvZ}~k#&HO<^fr5X ze1AuEeR%mk`;#m$jYR5+^`56*C)dN!r4T!}7B9pje~wC9pwIhCMm?6T{63Z??w-el z`MHgiK&KAYVl4UN`#s6RqvD9C`HRYZ4?oZ&7wS%Szr9}fwXBY<&#sfDvL45aYP?@HCh)qjaHox0&}?}d#+J|g(t7DtUkiu1%4lPoYF=#OTTm@&zkiQE z*TJm@NM?#Crp$u0K~Su$fL69>U=(=NViC{RMGC;-nDuNPrW^>LVBf={rC~F{tR?u_W2Kn5Pm^!0WYfILY4D#W(Xco~p`hDN{WV!<=*~*_cYp`^UA_pklLgdF# zjE^*OfY6`(>%T5K)tMojSa0RrK=74C4NwqT45yxUfVAbGF-Xorn+2Hl1N=!xVk#~c zw>Z`)28PZn0^4tAJFo1iqTcxGy#gWvW#xyY7!uJtuSClURpMUz6aqLe;1(1pw2W=% zdjcget>x$qUDMx_VIlXD`jV--$1cxL=+cIAA}UFtS8G0R&^hw;;H>^)=;9CMr|J2f zmqYfSeRB+jT#B}12PMQ!B~>{vdRG>Cx&U?pAWH3Or$Mfq<4)=`qJHMNE7ZpYVX6(8 zn4sL&!dqGna>R#Ph-IN)U(|P6> zBIY9)YQrrdEc>w+ctsY*v1jeJ^*D5?>K8+m87oleTQff z+$nw^laFJq1M57QnTyAG7Nd0tcx zF;t1@lmdA50J*3h(LeII^7%Gn+RE&?RR!6@GMt&o=D-uA`g8+7m3{6Ad)O=;YWjAv z)Zur1qWrO0Ct77Z?c{pBVeDI4goupXgy}oQD5y6(!og?uQQq0_aEz`@o1N;%6;SZd z9>^_aJO*eN_EQF`j=b?IqVD}1`R{HAYwMotmqZnfXAYb4r`y zqy)HIq%J#eKf=kcF_#Qg?VZ%TSxGzURkK%y>K2)wBhvR+0^<%eJxEltDWQ6 zmLCg|ON^-x9vHs+4sv{es1qII5Y*1!uDoj~dRPYmki~*EtF7Ljzukisz_dzRO92)^ zu{0{hYNlR?Aasc_+NQM=H{yI%u5ldj(Zmp10Ss^rv-& z3|xJ7T5Ayu!F~}a%l1~nFhWi=PC*0{+XyvlJzJ6UE*Si^s+t-fk`P_fyIAL zksf_L*ak_t444vgR$_LAa9b<^@V@HHk~E|36?wlmuF8(BrMbA`0W}_D%0_Gu;VZHR4#bSJ9-fAr$r|^`;_-Qv2h?Yq^Yb}XWQ%6g zb~Np7QFOmKKQ`yLW=u;Ea+0y4n#)2ghdiFa$D-LTesPYoIZ^>>L8LiaRFgz1j7W4& z#iw9s^y{N=Xrw3of2c}uCHS#-D27j0_*>rFpVwIOH&(KnLNeh<=}==~WwWF)PcG=i zuO$|r1VJ?#LaEI)K99l-7+T8^ijwIb(@`_>rcHCdNE?WT=t9>(Q3vxC#bx(OJR=Mx zLgk#MrGeG&AjX82eHz(V$mOS0-@!NN+fEvK%EDQvfPDpo1YqW3Y!coQ1$u)jFw5PW za+M;2Rn;Ph$m@FxpO64tVS=(Ad03bli@}HtnP~Qk>d(xC&G)uoXa_sK<;A7O8C}l)29K!}>cS{M0x6rT6hH$oSzJK_mB&8N3 zRPA>!?JJs_wJUAGbeW`xZBZMlw^3<60MlAk6Mh{!X0ZpnUyRY?-b8KDVf>#7{O$G7NO`h9NUXiiO4i(K-nv_*L70!pE9i7_YvU}>qUzY%U-r2 zSLM$Z$8pA*lZlIKl-Nva8=MMAl^RR3UneGb*|js?%LY_QG%5r#yyN$3m#Ab3PSm*c zAWJSt?NF)Labt=%mOH8$p>y{w^VM?mLw$x%F6-{tm@Tc|eA8rYp?!vi3j{Rqecs!1 zB?l>4Pxw;&`>dY$vAy)*Y$TzDZp`+gE0>7Wr_9z&%TVsg74y$_yrIq{KSpCZ9RZkb zrBgy2tg|rH&b+^#2G_{4rsoKJ8_@ZK*hT!kn;;^1GqFy@P+9@5E;^YA0H~94WWkG_jrF@t?&Rme z9!uJBQD?`hzr+h=1G;`s?sXsaixB%yJV{v_l;i+?Mv2mGdcqI(oN3pbk}&j4+OB-^ z2`^<}0%bQT7+g;#XB;XmD3%F%Oq!E}#S4PhPM)@A?zt?-rtq>JM2k!My$RA;B#MQx z3#~D0;pIskNN3%I8s2d)bYs-|lv+ffcGT`o5(|NPxuqGqryUu?CX8)uPq5LVTJqzs z{Y&>VEUyC!_eE5_Ho-1D7n#%~Qsuet99V?h2jvNRK|;DZ3v@J&tr<*qXsjwhgM<~k zNi!!b;3`+OA#2T`^RKgeI^UIm+WQtAsqiG;y{b8U^FpTuRuzgYlp9l9;H@(E2(}bx z8Mhr?fVmMS9v3rG@1k&$vSnGg1eOw)BywH9WHNL~8M*j8?_50QIu1RzuSy@Hpwwc- zJ2Ly+NzKn=dR^?sgK$;4{Ja|@iD$*nm=ywFk$lSc2&tbAWRZHl53{OG#$2k_@!c69 ziQ;aq<1uGQ_^RK*^I%0oa+2RqfM4w}^}RRWI{!yS(1L6xv(AQHEs9flp~1jXqG>+NWZSv8*JL zL>-Kbr&fXlrKUy*0+u9;r@IL|mX$U?&q*IjEjvErYpr@|bGm+0g!mBdrFN?Kau7I% zT2mHf*0OA*mb+?L1W@c5HyMYvi&c`oPdG+#XbrraT`Ly*T?nTf7#RQE83xoupuih)2=-n&e)te~s(?vRxVIfRd%9 zMvnV=Le-b3rHyhOAE3V!?W03yD}y8L{WmF7ZH6GSAfnI^ORm-zc!h|OQP0AxzyFdO z1b4?kRET3_9RaS~CXTF%2vsLkh%P$RM9?!XuY_SPt4wul-z*@F`sAoh#BF3#NvZp+ zi@LaJe<>-B*J<{McD5L#w&=X79iv`@4Bk1g{Y&X`XPa(AqBNo{_Fli`M8CqCB<@tT z?+nX1 zIvd+t(=1{2qH9KXN};KKb^qpS`UGOophb63>UwTFetUFyg!lwDi!m59Zkw#Ul``rU z=2`i$<9C=1WgDw4P1C}^t3Xa2DYjYg{<7GgNUTm9&znX2EdkRH>c7oKa3nrN*H+Av zhIcsfZCVT8u1-!qEQz~w>2piU5jH=A(Km{&nJ_2xCCWKZTo}9e2vmi?#N%-?F{=B} zucTt`*RvKY)1s8D#S{s(a$v!uz@Me_vPSs{F_}&F{CZb-SOzwciK+_@3(0 z;&NH@A!uzo0dD8>gN>n`lfg-@CJ6I=UuH9pfjpp2;9c;Z(|rEqU1$&iiFBxI5jNKf zaapJr*`9kvS4zpSvDrpp-iIc`oi*~?{i7nY*;mA^iVr2Ew7^TU1h^3u!Zvv9KARHU z&mmOOz1*jtJsspz2B8=7ZZxB;-eRRzvsN=7G?6nuNW-E(Dm+eqlj zb=gHc^Jeps$-&P?^}A}jM|;$0J6`7D&wVxi+i4B$)Z-A&X`ba@A1he&GOLIjyH(YD ze&@p3tq#2V&ZVw=o&$kR!jOFf)_5IqjNCQ}#x;ESWDR6SetL%m4{%)XsJe^jZbw6n znndUL>UTVcK<~nN{17qlW`O`dUg&pihsHQTMy=n#X48OG2n8Q{Il};XiMZJY@%NeJ zi43%TrF<+JAg{iROVW7_L^L|iPhLS>7Qe^-(24>4yAL$M7sNt==6a_E0nLIFueBRL zYhLuJ6%b+U3@^6X-aGrkfkh1$c{jbFF~XN5Zgog{1kqXe zCPfQ}Uv4LQZZkiZMe^&MgNfwq5tT#nWJF&^AwjP|ZcdBCy07(F5y`fCuf7iYJX^u!HrsY4PPikQXU>N}heKU2X`_0!5c_ z8h)H@I~QVJD1aKB$KL_%LbRFMM`R_nU^UOX&9M9dQFT8iYkIq>b}Zf3T{bVD?R0lq zBVAd`n~Zhq5IJN}zJcoBY=&=9&{IV84MMjoEe_}>hVQuIk{Q|6ijrDlcE=J$Y=vVV z2&btClfp`>WHOfWtR~dOn?o++B$*Ij0kN(YYn*rvN?(E;N2^!D-8k&OQO4D%qZvzF z4apAJUPLsfFX}EUJ%!>Y5g=?9(H&`Os-NyQR|W6CT{upJhTcA#Q^L}&q~V9h>uPDf z+0=FmZTEb*$LpkgGK>?u1dkMnrfqOqh>q}mvDh+7SiL&x{W3{-5I#crEMMD|sw49| zhDy$g3D`5ns3_!GoL%2H)tosVarGI6mMX$rM}9!|CQV+p{F&CmQ=pZLZ+hm>yxP!kC|5dGyLi$eMMa>BWzKFT4ge(n0@j zs`g5l$rg1?&X-hT!6gXAx?{)7SV1C(kI#q(*~}IRP{PJ0eWisaPu&CtqE?buRWBCH zuR^QGsT!7CI4oOP`oX3e-O*ghz;+YS>cS?`QV?5JOdJ=BR_v$uY18lO@7HkLtGd1I zvKa2i?xOCNEqpTPhD{^RF*v#DiISI5rco9pBgZ+@?S9`UNy24_} z4s5o`Dy{i!vpE9o!sLQ5njJ4zYo#*$p?gl)tRtksuk>%FO+gg3c$Rx;B zQXqon)Jkj0AgfCV^_7#78A;p3g7G}R^wR?VsO_^`Qr7u2Fn*3HcD zNqu%0>k@}@sBPb|iujtvNkJiPr_+v@=6NAiA{18L!0L zTUh}~c6O_1p8v#luTFvWf0q?gpg*9R=#njQjHDSS+i0gYS(BTsY)voYUf(5%_X7<= zimkqa_F4hWRkUb(o!829(a$>UKElgZ0l*!6`(RpT%@m0XyURsGFH%aj)U&*3^9V(@ z#6qdocawWFS^FLVl%KoOhYwC}YB6@Js>l$Z_u`s%LY6{f0U8&CYI33O!OQ3S=jh_t zJa3fwsRq-%_{({*I6@goy)^VMyLy>(6+DaRqEQFPW0jxdBj%Wnu-m|uuu6Q_>U^v- z<7yTuo%MRVn5*TF#Z1xWCHoDR4T?|n1Gd2tMfn}pk6|geRP95p0|HydK+OD83-d`Y zp2EZrTd`EuDK9J&kMDK|&?Ndqyjm>OvKPksGwn2KB_K6T0^( zg&0q&N087&oS^VpC3I!H8E*Muo)UE=YY!q9ZwaS|Q$bXu$mx4Rs1&?8`68xWmKh>y z0jQG@wb7p#y9qO7b{IWZX4c54oz58n&9sep1wMHiy1}kz%$F}_+J3_C@|dHBJ-1hY z52;BUhj1)}If@pzqkB!bZ0t~ht}&oSg}v#^BcSi8Avwh=-=BR+DOM6|S^Sc&r&t)V z7T4sn@yO|WQ~|yhB#&lha3EZ_TcfQI&Wo_%Cohi>RXx(*`MSBLwU_+p3)d3(15ZbK zHKP2q3$L7Pv5`w(=$xpL>-OG6aagY#xX4W9HA`HB^&O9!?0{&xC>u&RK|q`i4I({D zENWJk#PFEYfo78}PLuK75mN4x=>9}-4Kn83`h*T$^5MN_pJ|rqSN9c2fZ4PoWzq2^ zj*G`?(lVnPS&NRZ#l@Z(#Kt;nFGn)pacQu2GpstPI2etwsQlsd_AKr9Wkk;Bs4g8yJc6i^ z@02lt+*J)?f4qdiPBbo0`Wlve3%D**(c-?)4KbdsUY>3P5C~peY)d@kOsYbGkj!|Y zK?d4D82|+NESvApLV&c;m(M6q3|^S{`y9VrNbznto^8&NXSWf0RDC$&`lI476{GR> zaZuis<$ORaY$lf_9)haN-yAE!yHbh$82?=)iOkycn^&Y0|4};lqXqK76*y{>q|ZW? z{hHFPHKoo1Wav^y<}z(W&boU(D$kyl{@!$y96XO72@rNQB%`!om~us?jRBx#OB>eYro-qUXWNN=Hk98~&ID*Tmnrb{W_ z6tr9vH9a}T$92f56y{Ay)Z25zAaj*qbezyE4OtLX51?r6ef zqxq2pnQ=?Z)XBrz(po#zo4+9(n~=PsgYBDhI%%Hn#6-Lto1Sqvwr&N?<99Xpsf8%& z`KGb2=c}6DbB$1qeFsITO%tS5zSl(`+GVj*qRTLAHqBC!yvhVDj&UcWELED)GpX}Y z^AsSvivknmE+krhkCqFy6+Rz8@O;7De=>bf$>nw%uKfVjGvh%pobxeK%y1$49vqDk zYQqM@<+&T{ejJFF=V9BtzA)x$T8N39!|Mjj_)RSfMp)Y2ya9LSfH2n5oAP5~2F23Y z`lU~7mO{GxeaGp!*=@AU=ZI*Rn`c0kWP^)R+o~Nmz9gWhVNgvd&v8Rl?sGs`m^OEB zHo{$o$KPSgMI7)bVB9w(jEV+x6u6S=?BzR`b70=&c+>I{2xL>Dz6DYn7_kx3+{a(- zJagn(KijZbg_E4}_y*7hH0n`mC8u-Mt^1mll3ex)SK8OzhS_g&Y~!l0Gd^4SQi^Np zTOR>zHt)-N8Bx^I^D(T57r)11sz3#cG#7oRYi`yFg?Z^rQ**t0PxN!WX$P@u#}j(i zS-P5*iY^kx(H>i`^wz4TN)j_RB5-=~LWjDR`ZJf0tdjVYMLRNJ;5J#JH^Hnie`9kD zG}nBRoW^^IsP!93^lto0Y0M?XY2N^Z`o1{MN&!;H%PLvIEZ70pmn|Dj$8II<0b&<8X-Sy7;$ zGbJ!?<&ABN0HIq84}-=&kc7-y-3$yz^n^Rol1JapiuBT(W*atYy6{LZom5EbJZ-@X>~GLJ;HEZs*^NtXd!d3dAo2Wa=|cyZ+9-q z0xY|sYsR2~WaE&PprXcS>6r0aTPyb*W!p%qPLe#r*kgbRxlgHsPGHTd$&o#E*JzBq zKCcII?-ukQY+Ra?LQkfU`+FYA-4*e)R^6H95Bg2a>`?ha^weyn#pMsH)X zaQ_YeA9Meb?aHnzfno^0*&r8Pa@lpaz-@I8+@K8?q)Y;V07$L#?BR3AxO*;1naSh_ ze?*9Tbp{WT1m!4k%=JfIN32H05apC(o(oNDPlP4=cO@l9bj4OyBFeE1SI;8>?jU*PEc2-28!7+io5cRhzZ_0|4l~!B`@7+x zHVPenkTp&;S($<+gryM~ag2)oBU6qM;X`Dh3Bm?B7YpLXMqsFU4+(;hDb9upJ zNf03%=I{8vZ4qC=^F92rv^ZQMM$FQEs%Eo%KqNL z`Nvw3_yv>n&{^mzQTVMz@jOxP8PE4oI-FnMmRnSOP#oyU%Ep5-0=*xC;;Er4vfA)4 z(LO?ct=AG`;w6!~`-+l#KQsrkuP3V^m?*DncvrifV3vkH3^PBe5vLDR6wi0570Sj6|oat^wy!X zX#u||m4Igc6hqV8f|BRMieOZVO`{uH_*@~&S%Ph{4Q61Svw$)`cfdt%Q#hGhc}@tB zi5}MMpIgpuL74i8@|=pi`eXkk_cr8Muk+Z}q>OH!XLbhpIMpiLvev1>TEF;iTC@L! z-qrr0mY~~HzY6#pWihS6y4=sIK-q@rJ&|Aj*;KpUDvy~ z82hQlL-Hp4{UGddP#XB*kl!4_(>-S_?zv!QDuS^=$V}DSYY>dTSk#>`m@R4aQipt# zC!_%XO*vT8&S`co@|i5}jtaI@ayK-{@%s~a$4znJuY34Zh`-yMBw<#R ztGzWsrDg&N1+sS(5}MK!3Ko>Fz*B_LCiMAl?mO%0|0}O@VKG}W}zkSGYKMV;5S|THnnwy ze=Fl?MFC6CaUl#V#>M+0?TL&l zKVrd;W1~G46aG;l#&?g{^RQfmSQOpkT?fw*sR>EEMjUu0pG}`BZol<*hSd?W`?1YvJC1D(v(oAKv2KkAe98 z>@CQB*Z66L3EbSV$oy+f#;%g*zR$ls6h12yvZt;ORJdFgA9%ZL-e1M88Q<`c&u;;w z&ZAhYjYo&=cgi@&RGi!e4no;(DV7J7I>aLTFuqtkUmWMKW{8E>!otGDYtM`su~8s_ zNck%;;JH(X<^rJzJKN2VZSeJS@s{m}xuyepD8R#TWOSSmtys|l$PGS;k?v4;9C^2b z<>Vh|nl43!S^;MxC2SQukWjoi%9iJCHSdW&Sv9nN{0lVmDymYOesQGh8qr_dBWe#0f`ArLONA-IkOAgfi0ZaT|o;+Cw z;EP>X51L`^P8r2GDbI*v2=ZK|O0d{nR14Z;XVac2fU8ON<^Tv;33ZG^Xx<*KvvAszT~$N(9i>C!}z2VZvi<4;wH|jPN~7GJ|IM1o4^aF+i|& zIl&`Ty2+j5DDD5W@l=WcT`J^MB&O&>c`d-vfl7&)*mxTZm@Z9U-c@~Wg}}K|i;|$@ zluSW;M85X5lepb0B4B(^K0E~*GhvdFl@Q88c^B4uAZ&DvkwwPd3G?ROpy+jJG#7K{ z)d+yk?;)S3QdF)}doHk`yy5n6Fza-^Pv;y!q{mtTjcT+h1`jsR310yhMs+vu({Gm5P8aLaj@Baq@&R&}6`{H5eRoqVshU>za)L){ zl&0z(jl-nQQ7aE8)=M9F7Ep9TZpr57E^Dc>zn?v6N0kVR2{fIU2|#_0Q^cB6qcT3_ zoIaj06GG(mfpw!4IKjQ`dwWgarY?ZT^ELKrgLUW>dOu?JK}5`c?WJ^^`>Rhl_PDV2 zPS;QU+heu0<@P`f$s9Eo*I*t8$$BGoPxn5Br6(t&FE=fS=snc;vvmk*$5?d- z8r^}ZbU5+<+Gcx20CG4FHhVcZu?~voI1XoRGJd+>itutxk%_hJjIIu3<>Z;&v{_DT zb3>|jn?kusN!(ulOY537b7`hKzd8>Tc7~0=C8j#HSkJPMYan^HNMn*Vqw|K3Tw!(?6)NKPwWJD@yCO_jEWiB(~ZK%~=tzNCH-nw5K zrT%^2(+^}XYK&E3Y9)=-W~1uHGqd{qynIaa9zna>eigdw?%xx;@a)Ea?HG(2|6A?5 zi)zLCGDdZ<&?~F3$XDcI)hGnq{mPJBRFG?pFo~YVa<3gL!OJl6gu-Vo(4EOS%EiOT z=*npxA;zba>MM+C9!t#+Ur+abYHi*%wcQ%$6$v^eXOc%I-Z|dmSSreUA|OW#0!95< zyq(VmE5x1vd6se=zx&yV^H&4hke50cx7|F|;_`fN+lA$&TTfREV4unWuS_ZPlWfB_Qk0h2mDdlerx#=KVm;NhVcc{QpP|OlGm^97Tr&04zScn3mqRqJpv{=oD&pQW>e;^I8seCRQ$fAj*Q>o{P%~9FQJ*oaWYlyu z=}omdQc;5E%7OVu7xkp>ouF-Yt}a79I!!jc?qpki7G zLucnDYae=BluAn3RPiIwdZ5L?rDqcjf<8w;UriH@q(%Xz5UrZ;=X+km*fG>RI~goS zJ^f^?STMp;C(^T?38C8|+X^XgqwJE~-XD zEPKPCj=NA_1TOi8v;*D3r^e11R`U}8ayB31D<$SNLJv!%C|`1kZ)fGxw4IsPB7jc# zy?qEA-e)e);#pq*%oRg%BBVGKOz4*|A_!FE`@j@9P68}?$Abj&@*<20<4J)A#qoKK zS8N_0T52a-EOvwVvzaQ5M6EU0ial5ML!EYu-888KAa>`lk|3n;&T~ri2BJu!Cy0HH3B-QTkrGDApiZqJ&HLHk#B?QE`wHPp9GfeuqTB`N{9Fq2Ct8k` z$USw3R^E?KPkj5E=!Z^`nihoh{rw+A?+GaH=Avw!n-nQtBP->&pDV3+W$A+pbQ;&a zGqPHy%XRstxsBKJsX^lwsM6_T@T=c>E_arY9-uakjU@Jr4YF!R7^`StE=$hvlh1J? zUx+88IFgm_Eaz#>KmV5;UqX&6`6hDa)NhPX*Mc~XZaF0NE0q`fCZdRrj5B0Fg#KhZ zI6Y4^P{;M`-Wir6|H&R?#JETK$8{-Q>=PmjL7Q*T-AQ$oVm9Pzebw0|1@CZFSqPV6 zW6ROfuCiKoC`|y9B3*^H9jev_M6fP2oeqja&4uyJ-4pySMWmvBuGXpTW)RMgoK{kZ zinD=`d(+t}k#4Y|NuFtBxmI%xE1h5IcV^Y!BOmfPQ=ll2EIL*NmAEK35Iy>UC~~&k zz1DSDd7f7QO$IY`Lihll@i+?ab48Kt=exgGmN+A>cxU`85Gc`uRxLL4mQOfUh;U}* zz~<)lm+0DZ0i*Xp*$~ZAJ=b^x7M$e&Vs{bdKc0U16o5!M3dzT=^OYb#iPuJ0&@K?_ zIzUCfsI5w-g{T8WdvReN$fC{%9JO1474%J!W;uz_T`49|+8%?%EOWk(L_!S@e23Qe zE0pwzbC1&gn!SG&`5x|*pl=l!d~-e+R0y>#w^ZRkgdLZR!gs)4Os=Z z86{_(A$P+jOhJcbJ(&v$0omrV`kae|Fh-L-XaomTyN>GXjvj&Q&shj_Yh>v!9y}xr zL^^T3ygRVC5NgmK2DpO+89JGR7~TYKd=k+1!)GT_D7(C`9gGxdJXl}Z-c_{w#e1CI z@$?>33z{0Qis~&JD&k#NVV|ndxp?=cJKU7}j@M&l?$J7yIoO8FT{Y8t?IUu_-$X zcs0qv&b#HMLf`j|f0#_%36}a>Vd=FNmf0C#0gW}f29H2s9<(`~3v7XMSe&d)w-yWs z)>!$&`~yQON`%DP@UCANF{fyO)vU|ds@YW<-!SJXA^q#Y{+G7D8+v2A_)YiDzZe+S z%@@wpq!AC5`#7d^7b}UDWQ+wB`1YzCM~=`*pxz+BBlVd;^g>zNA*9P_yG|OT&j&dX zDc%P{953^^Qh0x3!&B$dmz2ug+w+p!ToCTF=r<_+8=@`7HV%4hH9>O?OtsnU{u4D$ zz84iVQ}EM$I)9Zq{!?z5N)*cV@!t6AVfQC>U~h^bU%$VPa}4+&wAFqRxNdjc&U4#X zuSh3vY7&5Y9DYTs+gW;M=RQ~aM@#djH+V9vZoH$D1%JyM-i5U7;Q9XIY2Suzap7$% zW9){O_ZGRve=`K!1~oJ7{hReMJ5xqao+Nu#9kSRjh7WE$W86hN`g8xZjh(p!)W6sz z;bve}zg_o1?Fez}*bZFqOIXv~YYh{gild;Vg~70MRt99WiE$x31!NqcpH1D55O@b7 z*%9je?4E|IFcHuJcL99pgp%J|=y7o>CCbW@}=0ErP;AR@s_KSQI3>Dieh7(Ru;gz5asDO58ISR;Ldn9{lOHiFPwrTU znQTnUPgqHa_y`J8yVkYGvXbwUg&9gmy<)qoPwW-5!aC4wWoj;zFjdqw+ICj_awa@J zE5B1zEIoyC{mT-j!bgz0MTRr>&m5YHTH#MXs+K%sF6#=T)sMjog7kvei}?s8x4@2- zyo&6V#m}9*kL;nb7ZabCy_L?`caA`Y26*KyeJe7bv}Ji;Qc6)ek#Nn8_8srSY9pz! zOCxs@WHxBD5zd#uPmpcrjs*-LY8g8Ii)5kdsJIIvMh@IH--wKD2y{Q^G-4#%iVf7? z_kO1}qFsi%Xcrs`m6(KJ!|zgjz?@D3dYuB`=Hf+&6FUSub z4Z{s_z!EmG^u_s&>UkwI%A`3E?{~xDZ4FJY3A06ctZ0lmCEWcrv^27UzzZuY-m~wa zSovNIgVHybX>bEU{I^gzg)BW86+q`1MXxD(F&?hj&nO-3)OfDgq&VDp65QI8i}%?D z$^*cG&rwJ=a$b_&=v9d#Eh>CZ=|*AR2BlnOvDa<}c$2b=bvBD=&qWHZ@NTfO<*NEM zH;M7sF{PAg{2e!q>HtvnRx0wxb*z)~=yTMk7OI{U@C2lGs?$*s>YFHW7x{aC`xmTo zubZ{xVL4Z_4>{*pfc(PSo5(Cq88sv3jF#O%TgLIYM;jS3|HQ%}djp`(7+8hYg2Gu8 z`KY~FK8*@4Jg%B*fmII<)Ov%^nYQM2>$?MrVnUVjU2a#3@IA@f2!@>I`J&v ztqAXE@BnB+tnLLID7njU_UenP3acz)u{AWIu;LufgfVNDHx5?Spg^`f9&k&@juj`3 zA8co0Mnn9aTVkl%*ie?Oj;EPkMyUfDpNQ(EOwpnOq1d}v0C>nOS)MDNl{E&@e+smU zH3l`SMdvy7-Xa9iCNJ!GT$%r;{n4b9k(78<^k|h}z9uR_e~UiY_yC41oev!o*(ij& zZ*ChCcv)_N>-ME2d<~m8rdM&MUd`SEQC$nsd&zkyC5S-b+}zgoT$>B&Q83{Y2~5^a z5bR(=V8V5{Q?>S_qr&H~fy4xNgeZuIj&y`s8QuCq%+7cgBudH4P=rW0r^h<>%$xcH z)nguCplo`#mXADnd7flxX+4!kkFGB=4FU?}f=fg03*TX0m>uhZ{Kkv2ixlH+3i2j; z*-0sOYg?*P)2%mzp45{#6sxvsd*vVzlwcwZj~B z_HNDvY8HkAs(LV8gd<$+35V>_{wOWBv>|HPs@LOk4qVWzKKJ^w%CN-66xw=+;6H}M zl;WOj3k4V*QHM5Y@I!Temy_9r775T1Eyg@T{;8U-LtH|kIH&PZtR2N-gW!DznhasF zuv70~a7CWo;3Aa|h#tjf?Y=l$Op@bX`6tMONfkFPVtxo{u!{~>;;r=-KcQCY==O$E zeM*3gvitytepv5f?pq-2cjs#@B!M}Z>9tRjTO%Th3sZag%({rwr_9{J&B0TA-%(&B zIpd9R?{sUWRmfqesfeG2eN#FD?~MpIod8!5ekq>reF&)ljoC$^D_OeUgmv2@1im+L zELhIX;M$mA-fkPxwBE$)PybW=&#F)O7viL)(0zt36^Bxm0a`Pv{^P+GY z-KSkWMlAy52{`1wf9(sk%NM-O{ohn-|Ju)O7j+0KZrp|9x>|%+=vQT!EH*-U(@0ED zXo|*#?)Spo^A5luiCgH9p^ERj@N{@=)?43iaUO&qaiXSF@L=d!=_Za@R}g9t!p{lb z>ql2!d5a6$KvJIM>xn7g3iV#(&7ICk=Ye+NW^eej6L7fSh^`tafU_a+QVv$@O5O|#Ld|N5nTW>%P!PQefN z{`G%1z5i}~hs$kO*Q7sgblWfO$Q^dVv8(FO$m{Fgrh`TDbMJD2=~LA&F`VQVxjWQq zmiP`6(T5R;NkOM^63AXT1}1o?V#SLWKo=Hk;-@L!B*IReHSUc9X*Uwu%?q%stcehxZ0 zivEaroUeJmz-vW39%NAE{6B?D2mv*#mG@7o>&5!_-WvzZ{(~hLZ-<!yq ze6oX;7*yh<+R;$<7leJOsEATWO6_@nZs38|B&W0$c0rDAb4LrOx;f3g$KX`MhQbJ? zyKz={2KVWsd9Em8e%l>ETOFg8?l3 zCyP-Xjs}3c=xQ7y4XD*zQMo%E9W>Nr5~{>SPRa$BgB=Q=?0~dai+7~LzxM>{6x*1^;y6v$C%L{$7!ESu38O-ozx2!@g?>IWQvz|KHi)gf(;0Yn72 zT?n@xdtL+yFh#$k(col!dE-p8((+nkX2Rc*$ebNoB}Cl@hD zh@cxtVXJg#SAM{ipsBLn2fstrXHef|@pV=&`jg__J@f!~=;Umh>ZSkud&|PDutRjh ztw~|mTq_#_s(_jAr#eGwt)0u?)%3cPSfDIidghp#e`){_tfwq#3SR`Tv1qa6%6ZTs zbzNie-l{jZiJj-j6-VB+?v;1BU=>vYJi7n z!!zWsj+D4~UlTLM-!5S?cJnEKhseO}@e!lS)Nj&Z3!f$fkdg9oz>;1-nkOaMm&8F)Z*2s$c8@eVA;bPkIClD-X9J+J4t0W>!&>L?Mpk4q zp$mraA|~wou9v#?iMny0iY{)7PSAvps?iiJvp zRNxppR2MXJjvUIe;ed2i&n~+^GIA+*isU|iZ9Ui4(6{d&6L-eBI5ry^OT0@D2Bgm@ zVGIn)wDWG1r-~663Z#hjbKusbLZ%Amo;V3MA*}Hm&&9wfMN{Go96!aNEIQe8N>}<| z{&XH-{LIY+_Jm-}GzA=kBP4uvw9YBP)$XCea6(V0bgqyWvdM3*$4SPQZ%=268d_Ws z-NnY27>|R<977SznP|&3RY&u8EvU}c$%Ku5BWz~E1btu8#(^8C;Z8uDHl3m*7F@+fC4I-zHHEV#{DgMBF|UjK%;)ca!RkM)m;bi^L{K%) z)6MUB9;-4yQJ_3VO(iQmA)L;P{ctwW7WMyr-_!p0!IQFP4!iFrg{};c)hJ*b&W{TJ z-f(T)K&Ge!b*caWAOJ~3K~$gqeKsubc}ymw=@)rb{*Bo?BMt5tIjJ`)Xc zE%No*pJP^B6ex-kM`8m4_&c^-d`Eie&KCDDFe~Eg4_E}eWnvM zq$rXp_}m08R+<@>C46w%F%^?-VGSiNxYc_p16|fff^b1%L6s5eUEI{sA{HB(n=(|l z8P`WDK%-QQ<%F4xW;`n-De;ZC(*(b{gKs5^zt!2UL2RDy;i9q{6xFw#I4%kKEHe7v z=cJ{_F}%s*a9>o{IVF_wq^84oy?Fn9bIP^K=`=7R%9@yaUtxhq0^qmGUXgcl~c{HGcDa`!E)K^0@wSzjzq`cfwL3 z2G8pkiSPZ}Jj|;YpV4_K`s0tr5&de@!@zvk+Dl%QpQ`dzm@aMOg=-W{HI@QxE3jT31SW|C18T4! z)WX!Ix=dG(tL?q?`=(q5RJ%Sz1%CVt5$CC3)esI;#j-K+`71mor58WnY%-V*HcZ6r zAu9Icc#!A8yZn;KA^B57X9-0PEF_zpVMhi~>qOq7AKwb|Swk;Xm7g=R3x(h$(4`!R zN*4)-X4_$8lzk7tipt?Ja?k`?ebe*7qmn@d-EuQqabEI28JN&Jb zG?dX3eqZpVA>b6gzjN_C657uD7G!ut!zP{=Ht7h*nL{&qO*=$OFj|N$Rjyqt2z=%P(2(c?mOwS3KP^3;@y0ns zNWE*Z=g~u*9s%H7MXgRCEK?1}Epb;H`A`mt5cD^sDyNX`7Z{)z5-tq>YLM5yg;l7T z5XSDLG&Y0e_t2D3a6YF67QyVuoLSnYxoylookIy4yeaBbW_u3$IsM*SZB9jiX?@>A zokSW^cLgzMB8Smes{U`%IG>It24#Ruej#>^m@IVMNbz17QsIzNf~Yf?c~&(n=Y8e7 zjJTz)dP92HoFSn%;*S+?VncCIH#sQCWp<<5f#_OuRFobSmenkDdz28ui!6BTJQBb_Z zAzF6=kNTxXLe(I(%tb%k#}0Y&u3CTkh6;Oy1BkKuo4?WAK6xzER@g*WQ)dX`J*w<+ zBEGH+me2JYx8k84&m9s>9NwpGP4$ZN{X@j5O&Fq0$SFa}WYoM8YG;BJSq{Qgl^g$3DzsmfCFR@aS=`dM z(wXDN&ne}a_xiHz*Dm&Q#Z|NH!Pp)u^aZ+;ILWJ~T~-#x+C@6KQ<@P;(c0*a6*ENm zFgmi(N`72n`cJ8K8}!ymXS8f@e?9n|now6Pgx`sj#7Qwyx`zDtq*QNsk8eVZW_MND zU0Cs^s{O|J!JzynBdH|>X~v1htI^8v+-#-=gA2=j3hqK$%-s>`cwbaImItL6kn!lR3zt@$ko)@ps2`y<&PPD?>E=qc0k-1H#hp{g!;+`Ku)rbBGRC#BHjdw z@T|DL!yB;*nMFc@G_mG5AR7}H*C_3tVejW-k`8iNkSJD)atCs2AYm0q}<+TLyrNjq5ax9v$isY)Nt zo&D|^Y-S44d!sbHHx=M^CJJMvcVozU%mktB6xqqpD2S*Ih9PSYFngxDTs(6aj6oRB zM30hn*F>*3b=_cPV%#@l71q`R@?z=K5$NELckZHKn7A$ga#)gs$0aUkvhA8ztR-jDexV-@Umqq-ZHKBFEkp zTQQ~p{2$nR!l;sZ(VGuD+B{b_6A}fk2sE3tXkDr`5f?y3%fdzQ1tJiq0>_Zsoy`X2 zpLye@ngCqs@0|pzR}a?Z+{I!L1xA|{B-$KZ?=@o#nN1lx?YIA%^{mn?-rO6l?cH~= z+9<8;TJ4xY_YUYuAk};;GQx>~(o9gIC>{WkA&L!I1~^sM&=S7~ zeIFh1u0yw?4Nwkj4Bsy{p)Qs*LV-khLoyILzwfe?(bqdxz& zR)zIz+5;+{iSpNq9K$X{O^0J(_hKvhKi7TwifOilIvNG)CPVur+*igZ$Qd2PY4yEc zaGWS*2}O+Kyq6H>Ah5N)ExpzdS6W>^>776}<`gC6&2ZT6b}`i3N>8_Iox=7R^hId z)jcVqf&pN?a1Cb##^*-t8#J`bL9n>6dS}Bk{<#`YL9Nj+XRQ}&`kb^#uE+{}0(cwc z1$Sqf@Vp0&3w0KRoGLOZj4QGKEt72CkwzG zGn;Ff{sk{)ie|Z;YYnUzOgMI_%n{1pqmn-%kzPb0?PA@nvo`8WABSMPXrPf6IO0 zi~1+Hnqcz(7I6;7!a-G=!*fmLiDaO-r8wYObHhRnf}U zzg`Ej(L@BCkpt8tSu0@6UE~U|E3h`9Xb9areZaZ8U|Yli=!5P-f9FWna~~qG1rv&9 zQdtss83Iw7#|;_^K&!6iEX7l^_Vh>$i;#LxRSEx%tt9b!ayC7oR|He#xC)DR9jB+beP+|E+EF7Nt=V-P=tu zTfVPCMeKUIZ!pm9oWi=Q!o6kdx~=ZTy;8<)yQ>ZB?{|C7f9ZhH#-lD>Rf<@(9O}8} zGS{vg@vc*)TiKhYI(-LbIOu9V*f3H&3J`bA?Xf7dTPEAZAt{q`&aDo znjirn3BI4}Ki{pY7oT_XfSXF=jBZXcr2a0Hje1#fmoT_^5i?(f1g* zvgTi3Bm>Y1t6KHpDj?|Kzf=xz;R~=u&!|xrM(NhKfGN*lTg{6-31R-M0=X^3Sg_nT zv2)$vi!FBEyf4-$5gkX1R|y~wSOdh```iy~O@~7sEq@j^uOJClvO&xSS>-!gxdXIO$$%DY)tsphwc=bS7NQ>W~5z-ddc6&1Zl0iEydT>|V zJqp_J{n{vS&%Hd*ihuuIFz#a^P{2%o&^^v#R~y>n~+t-dr~0`Bd72`T2KjZ&Z}Kl&r8TX zSM#II7(o~+EL7>u0@Wj+(9>ht<{gf7-*Tt`kw^x?O=~MV87OAO>Ia457EsbPK8)Qk ztPpu3{65md64!$bni4TWQpnv{T+APPTDe3TlBeC25l?IQK&pmkfYvWpbfYlNBNqclH5^Dl(Rnwk~`s8$XKD8q}{j^M|Cg6isi!G z$ngX?{3d%UiXVZdEC7I@d-I&@ouIMi_?3O0Sp&Wf&_v`5}pA#4en zh|;Z~vO=)UJu=nv+!UzYq$ME~{FIix1zo$PbhksQRZ~+BbAg^%E!+>}5?`fkVSV8t z{)hFzaM=xHb%f-yq03`Tf&L|J9h4q$F64{90eYelR>0xrFr9z`-8&SBxQQ`$_jx^q z=y?DFdmd=f|H7DDy!)asCdB;~D|i~dSI-on!`X2Kpi7FPX~Su_6_f`wGH(?j5(|Xh zpht$7C(o=SJ}Mzc;l&8;l9|!0_GG)To>9R-*s&PPG?JqB+?Gka8qpGmpS+sk*`g!H zF^4D1@pbVY1V|&hdAfN}y_FEf#leV={rUw~aAm`&Gi;{LgET+FIN?1ai<&3TYKWN1 zo@~*%kfN4NPTD#S6SH$={uHt85d2Ia0ZxEV!8!8@X7?B6c5BSo#rvLGR;I}HDQZ2| zX!*H*tJ6CQ4*!HV(m@-yofChQQ&D3T9?mtL@9P}{sFqE4bIQFypW%}Z#W#nHw z7Jk{->bIv2_A7V5(FL&?Ax_O($0d8ZPSu9Y^XKij(Fmts;iGSa1-P!+e4t^Erx993Gzc_?%JHu&Igtu*M%QW;iE|vBu#G83N0=JAZ z5?a*ygL3p5iE8ECv{t{IM>TH~{ICs?i7UP1bAdU2JTK$t#03D=yA6TP zgoD-5D7mrM+6JZ9Y()RHhw`>x(rm`~zrn5kWwW{U#gRAdX0SWFXw{PVFI)^aVZvXo zn|p%s&-a;yzK_3~S`+;ztKe+xm{m9RlI0FX!`VFt#*GpbpZSfj@ryI*cJ3g#>5{NP zCQWFHlY-;*!tn3^)!@eud&v$_fnSZ`X`lGvcjxuPku%_|JA8`t{8PH(upa*F{qe)R zo;t6JqO@~Y-IUJg9J-ec@YX8-U&(hGIKpFSWe`~SCFk^uS2lT?xDa^1grlbW|Bv31 zpVn+L7*5LhmuuBE>%#N0i%8GOTkMTV{N!!@pAj**VbIib;&*}=TBzKTOWB1HgaI?b zeD3n;90~jn0{z!$&o7=|BtbJuvvA>+sx`1Irv&sztsG}yE>5R3UVlWReCAhGjK7Z{ zhfh_=cgUa$sj^g_HyHIMwyY1vC}lx&piIMW37!hBt>NrB}| z7Q0_qUjG`lzl{voQCQ!H$&s(NZ8E@bvg#vc_L5SlbI&lKL`9<;K z9~8sMSiAudn!c~m1C06CjzG!B(EmBL@@v$m#az9J5W&yqyS6iMhIC~6nyLys z6ffFPGoflZ)Li4z34@qlbkDLLxv@M#)h(E=j>iExG0lZCY)s332))Aqb?-k#cmuLg@UQ0>f-pbni{Xmz8eKjL|)-{(2*fz1LqHqg&`hZ$Ua>N@?xlp7kI1= zXZUz&^}s|?oIO?dpX;Lqm94RCmF<^LUF;8uz-uL0c|TCjaE~3~S@B&wS$OKcyg|0b zxbf#bbT&D$a&2qTGijCA;^jC8+3MT#xvfeeGRvBQ{>u&CD4X8{zXs1+6wdA$f8SFd zbO2GIL;u|KQEYjSY^bnzUB7+Aaew7;1>6Vc_3|*kdHsO+{AzWp`4rZ88h_p&CfCZj z1`>O(=W|VjUa2zO(|T2*<=;JRVI3^DfyX_8s#N*DeD1!#LFQqo%HUbkxYv-V2?J!0 zTWe(H_0s}7%EZ`MqxLz=uX-;NvI0deB)mP;eUm*)hQfo8H7@p1kdGm{R9Qk9CuNxx z{#Iu0h)vvlck4s?1OH@R#Y_Ol%(59b#S5t%6MR)uQnjBeiew&XXP!?%wcb~qyMREN9v+2H?|C>4dO>*BH)BX+C)(QH#<8>{eNos%xzjNM$7`50S zfn8AD<#S+(INs`dr*MR-2k`*IR@#!h5~#On7l?vL#X29>P0@gs*!Pf($NCJgJKyh; zs@_Z&vG&G_0BmfTQtvOofNB95Q=syhFssFBbmpDr%jw?S_o{$qbWU4EqrZCvmB?*s z6Pq}t&1x-+nUrhZtznT1odOnCUZTe&%FPwec{mHNUJ2hxI$5W*AYKEYgaz9iN6kg# zfA^j#27(#11UMYB+`@N1pCF4-S{dF@Sj>Gu_mNwJWdQWG1`TerQvwb{lQ$P`4Ncun z##wDxZp>yVOI#(xOe5!%F;cbALdDwzDJI8YDWfMppD0f=cBc>=*z-RY+D%Q}Vt&jj z#a#;Kn&8!i;K=UuaMPpg)*$PMxmRsBum)*J7>iv(PZsc)1YJrfE0T93IX< zg?|^QMWF8rz{12;9O#U|g_sZ>JgP88WZwfaUm;}SJWf25Eq1MyI-dkYiVh$_%D!fg zbg~XVu6eYnG1N&xp;>Ch&fMEdPEOBz_~?raZ1$@UGPpZqF{i`|y8{71vp3Xx<_G|^ zMZ$1t%DQAS_(Bw}&@M!&t1GKpobI;GNtAV3Q;~oM3M_@E7=)VopUMIEeq*YZg*3O# z23nJo9W$!V++sN_smgAjXG2)&2`AI%u;fkK;BQ5iE`|CpT_@y>T;W9A^oAF2)$T}# zqHo4)y9(~I4>n*h#8bo9%7sJenf5p0VAa5Kvd z=+aTZL)(u^PZn`+IiMBAK@e5`7G;>VjG9G#xWMN3ihB9fo$xqRkO7OjAgrQPrml$Y z$U)nfCj2E=`j(#oEsAe49~>CtE;>gKh!{H}q&j=&?{Bh5gcXPq3#b+sRH&Ar_Jid~ zR`yQmWAM1FdZgA_>te^Jdm}7lsIut1oOsVAJloDnR@Ub)J@XS}Tx)<-Ls`PgO_J|M zcoPJ!{@!PS8~79|d6p2ss~6}1MGGvEiTI5{!_Kl?3k8ikY`-g$6f}<6cv>zowu}T8}?jwcF=PPl$jAi&??cLtuuZ!6G-*6qku!)KbF*eSj@u zeWPcMl6okD5xKT6y41Ckqpq;(qmTt=Q?Xihw3obEk|kBhsn8hc5x5I7Y78A+#>x{Q zoo(R_OM(0T+lxhSBA8N1*$pb?$4F!2acGJsk?V<53h&)hVJ@S+>aL^F9r_tmIyVY| zmHVGzX8#Er_Jt_6&Bm7Iy^FBA<`7T@PlMcC(NfXdh&3#n@6U*c9DR>xAag+sPBr)T zTf^Me*y7A$2Zlio-*x9JhwGaRp3PV?d{%upw_WSv5x>T$78~S=THfoaO(pnX?|vX} zo)vt-D%Me*eZ|^|)6}65N!yfGV?yApk7`E*MBoLob3$zx5A@i4T_t|&orb)fGY4+S z`us`CTusJs@PK~pzZWTAADNX-*#WDENY-hn+7q@6r{33W0lTuuTSQ1zTeoWXEWOVf zLh!D`Q|K&9s{*J^FLjpVGb|*XRWmi6C5{|rOh+qhbch-(RQ?8UYZZksZ!<^Ail@XY zV&ovFYBG&9f>YG@lE-qyRV*V=V0IzZK-coQ3{E*QxeHrA#aCJ`sE zgJa|`?+h1a;K7W-*yX-u&oRhQW=gd9n@@3A?G=vT4`z%X5bv??>p3t%g@+0^BI71( z#ke~#at}XJMN>sD#Me{W9U>6@?+Eg+_CQx`1f=WvJ-Ewdzt^{K8r(S?)~*1C`Fq%M z*)Svfxt@d2gPr@HU5FPKq9R)PQDtwJox7QHr!mM=iq?adzH5+i=~9Tz4uXqZ8IyA> z0j-lM&pW7@W&g^lzRqc0I%hf)Ret`x(swYk|Crl8d6cnVy>AKDefN9LVLbtl7ZoQk zDrgP!?%4N;KzqtF)-Kz4+LL$NH$>y2!7dvKPJcNmVkcVo+3R;wqSd$dM(ujRI8gWp zN-d(887{mDUPWb+R8!#{%iou^O>q`Cp&$%t=H~@5SSM(5i^2oaY1J4js-*?xkq%1z z;o#k}x27PBMfAYBZXspmXV0^CNo3R}&7SCqSg$%MI!ZI6Q&l4aV(!qU=J;7%Yv+Lb zA8VlHb!1FHy@!eJ4?@ZkfK%NykZCf$CXR1Oiyv)4m^DIwhcCBSLZce1-=f5RW41Ql zhskR{s67zM*jpiQgE;DZUmY-2CGi!QMT=udKxmBKBYS!~o{iCa%USt}a0gdWRaHPW zU8B^pH@~r@6m`&>WRkI&rqt+a)VFMFIQ-q$iNLHxVNpzoT)2leQh-K9o0M7TL_NBI zr0#}>_ptaV$p@ilzK$&5qJ%&}EWz+e0f4p&Xfr6gn{6(a7HSuzQ8^LHO^|J#U%=mz z4qiZiy_FbdeeSg_ZWa%96Yp`Ya1A3itwwA=zgnhYN0yj9Z5c<)5kk!VL>XZNErhga zITNM4AQ=Ml?7=SbxEQIcuOX@Px{^(AG!~@Yv&f^vd(gUoTs;q~g#N2nSXagya@Xvc zqbW6*JcEqYhUAM{Sv4;wPvtUyq$~xclk5~qHsPWELil6W*BPDakIYVC;#onu@Ou0o z?%mDxzw$#g;81>}R#hRP+Ko5FWMkUb`}z18NXYKMZE#4POyFm~0JEv|M~)6!?b84N zAOJ~3K~xP@=dy#lS8%KyF_roZx?{F;p7;O$HiV4~W~?^sX2qV)N)$tjR>1BN=7$cA zzL5p@(Pr-Jo+NBm&urFYX6!)Z>3)~l=Ou1`5lhz0w&P6GY{1XpY&7%IokTx80;KnlP z)#Yd@T@vx&7wgZgCk{~_QuoeN7;N=^dMrMZI6>5D$ap3Y7(T5BLBDeW7tpbTJ$^q9 zq(w38?u}LMyiUmb-WrGkrh!$pDPV7jE36$dh2&z-GIN@%8Y9eIUe++p<(^{*12Nbo z2*Cg#1P4H&u`Gzn_oCb%-!J^7W%Dd(D1vG@+l^H{{3Gn z=>QSKe6I~vYr8GfUtpOsFAuRnX>C*TJYMIiB6AmHdbR1^e7~E=@8i6ZU~$TGz4!Jo zRu8}9H8L|>Ah?%0ze#b&ISK?VUfOTNJ<{HycVB@7th6A4_FTHR zIDe>l#I9A#6%)`m%(!ygvnU6DLdniSyWPzc1<5wLzJJHF@~%NVfAPM&FM@&bI27-D z{t=xKSq`zqZN&B$J#QNz0tqalJZI#l-{rIKMEP$9hx}h>>0t=7{Nf?qhOasXusF9t zDu9bF$x3dssnI)N2ASmrL;Fo^rS_z6Bwt}Fv2k`u?}{dz*fFFDlBkxctyu1Gsd!7+ zr2$byS7+i)EA2Pca21vIp&j>Kw~eAwIvem2E?Xdz!c_{5;B(Oxt)y#^LR-jDA1ZXE z44tgGK`l8zy_Sn)78+;E2`bdYIYG8^%RHTcjQCw00&Q44E2Wi$;S_mqqk>g3w*esC zGHVz6z`AHmV?e1nlE16#^(sl_0YmLY@rzzBRP#qHH0-*3ye7Wx-T4YtfmK<4|(c)QV za27hs^Eik8wpG4@0)k9|g1;}55R8&!VY9JNc zCYgr0`HB`{w0OxI9MQd)%`sY9RJ1bul=f zh4?H-AK7!{v%;A43}jQ9$rT8Q!gq&mKriF+8lJ-;Y*l!2J4c9nVHpLL62K9^J&rxW zX?vfSzoq=PLwe*SlZPBSZV)X`MX+{w4-5%jT)h9_oOm0;pQD=A7wAE!841P!$&Bd= z&RqyTP5|cGwRmz@$qT^j4`?kjww#Ovrxapj5u32KPi%Nrg#cWH*j^qOnviCB zN8uqoiV`Z>EUb5HQU+scXla7!Xu+$osx{*G;=Egamr#gDR%xj`NcVdEe{>;ZRs!nX zm%-i?cC$A&#(H=bM0yrf>dH|b>|%bjU>+XAz!=(&Y~PCSg;Li2R^-P0Q41GE_*g|0 z!k*akLi0#t@$k;2@Cz?M(|7)Yx`kt^9e_CN@$V5uu=h&S%Jw0cO!xsl<)Y!dZ9`1R zT~H#Da%-XZRECOz2Pq!t!TED)>r(t0pI`sihY3jub}fZdw6;-}KEYJ8%0pAruM{OZ zFp7gw;%bg{CNLR|s!OG|eyXjQ3zgGZ6>HwrZkVZ_0|a!xT$=^)u1bAC_N$mA4Bk*k zG^#&ql=Tq4^&djy4BL7GgL<<56bx){8TR+MdFKY%Y_zDSIXgYOd#4*}Wejx-on;DX zx#D7-aRmZ3Q)Hr@I~Utme~;0lQSco#U{T$@zF*|Irw?r8Y5u`s%6eoinFM@K2-_cR z{JV-e)!U4t0#N<))`+zCS&g{8+3T9&U3DL2kEuKPPIGvw4@$l6qKVAR%6QbO?|07K z85MT{)~#1Ofo^llBJVDU_o)8kncF@ws+ubEwt_u!SV)wj06R5z=-0OWmt z>l~hhY{+$kG@B|UkSYdNXRSj4&g57V!do{brx%1R}hio^c>qD;*@IA0#2{h4b*c!etv8{ z>Wm(4+vEM?gHh-QV}}L&62Ic7p>4j@<`Lu@-GyAI9u+eyDj%2EsRTcZKof7!H6Nl; zZ%XZH&j#>0;FT+`y>>{f3*#@||I|-@Q7dX%Kh^>84TkB3|?3l#tB=xHIq1D9zZGqKNv$Q`{u>dym>QwPfT`8{oGOjJt)A={5?g?rEaT z;gmkGp#YXME&P3VKLu^9?n!YkD2|P%t4Q#R078C&I8`7GfyAj`P3KkXPoc7VLqwc0 ztclF*2!4}}%y%EWzg_&BtX7nk1~eg<4rp=;bZXnqW#4>O4>X=aU*18qTs)sDaKV*u0f52(?QG8XbI~7+CbZZ9 zhkAj&f$ySsfzxdQU)y>-NV2d^rjS6SGO4h>*xtc=p=3E!;j#3hcI_8L(j5~*Me7|2 z#7Fm>SRilH-gh8H!A%h^5DGi*s!LO$P-!HCrB_v}l;8+-J^@i5Fic!|=kZA^dAX$Zf3|hN0BwWaIHDCOb z1{4PkaS~2aP>?f$qQN6qjaANtmP|`=c=()3W}^y<`dYL?mkVc{4uua7)IE#e@I!tU{Pevh^QSaN88<#sk5HYqG{hEv~Q> zPXH;!sw8?%X*Ck4M&kQcVSKSMxwk)+28gh|=-Am~uY*jA)g!mJHU{%y7PTPC9b8IY zqA~~?s6!OueneuEyD{?l2(MLn%zGrU7PrO^)S;Y=F0`(K9dBZLc3%I;l_8Xad_b)y0Cif~NSGn*qc&<1c zimvFk0UjUo-_Xx4dsApP=4@|^;=MBPmG4Z}B1n!6u=mjjUog|)GTCJUD+Eg!`@#f~ zSQDyec?3U%;19B}eAiU)x7Zj$7Q^&But8%AZ$#|@)1Iti=PY|>dWN0@z=!#3=1reT z%&8R{IB#&!Ka&hYHT;?gr=HUWzz##YMnxoccl}?_G3|1Ka17^wIk$~CwNX8)NZaPn zf;d@0PR0pE6WHsqCL-kl=C&{vaw-JQa|N5cQ|x!=-*g@jhCE(d=-#A46n}l1Yw}1t z1C52AS3&2zxy^cNKCqP=qcRkEuqJn=tm?;7bJcFGENR+TDzBKr#u)Oj!t>~uFURwv zl!mwIt=6*~J5^}DY!EE68^o${dhB#Lb@CWRbjB_p)CkbjRfIzdtTRFyxpCV@S7N!R z?w^x;tc$b#x|52EWZe(H??V2|dVYr+gDHQiBkN;gKvyBlN z(=aYNh{gT+xmAF``8#=5X)apfM<(SnC+@X2{bd?C-z&ldi(yPqvi)7Aiigu_u#f6>{+`pYTnxW-Z)jph%g?%P#5kCfV;bbZPz^z z;bq9U9X#s(UaxR*Z}u_lJ3hx}ut&r2xh7m|li@+{jFu|vM zfVhuJ0vjcmRZ@R?-}w738Lg~$rvY{h6>IY1fHCLo(PAn}TIFUS3u{u6kPV^$cj!j6 zRj8jkCsgV8L?nQx4AIHz<$O^BKcNHo>X$bG3#xh&&6(f#+9Ah49!-@>$*^G~h(Od? zA(s0*itv!zYAf#t|xZb0I$ucZl>(SuBE<@h=fv-{ml7X|0Avdh; z86;!rO-RR9Giq}V#*VF(erIe@DB0wm8|WT>=Cu%Y;0{5%*y9mI3UFl5e4P{Yv`1ih zKG_tDhJM^D58T)yqrtS44wD)OV0^pNMlRW6%{z&BZZx^ zMg?-+iE?VA)OJx21QD7j(2uN`US)~58vfh+kxZs-umvjukOtCflz;3~|2it33@cis zsNjl$8FWU9=uDyGaBVRtE2#jwsFbCCc4T81=wwt){{Shq>NLhq_VFCK*xc0A?rcbV z_Y8p3VYw@VYU!ch?UR~*Ob|rMfNwE7&^wr#;2}(zd5UiWX~{nD@rzTIHKG?~+uz!6 zw#XteYfyy8V7=D{9Z@OmHvM3yR9&fCpbLT zC*^M8aWTx3lu#UZSk>2BLynMJyoLpxyMHaf`5Dk$adM|5)hGA-s3( z>7Ed4&7bXe23lhxyu&zlwa>$T=kK3?yE5uj#E0&YP|7`vkrf<^cy2NTG6D-)Fm{6s z2yLC8^NLW@7Yxt3SF<7gS-;N>_XIvADnJeyyKfL07f9*umQb8?P6LL~rVMD1zN7Zf zb^E_j+rYf*RO7pUt(iRz522EP&*0C3TF^rLtS_p`>&i1f0H+M=K|Oma7@+ts_RMw?@H>p#I$lBbGEO-?^C)1|JVQf z|MUN?l0Io>wr14Ww7{4N`&cBToQ zZ&!cgIzrwFkM!HF2AbGnMKXJO>q_=L~377Xg&7YZF z()ewpKL-tDliry{dkLLM9beRJKfdm2C9>wh{g{`(r( zkNf@C?`SO@|2X^!{{sJTo_;+K|I<4DktG?ey=YNx1sp9`j}VGx=Ds#()VM%%oyB<3!Cgt9BGTf5k ztU;LTb@Dk4X(J}W5xV^JyI%^sZ@NsTIx9|7b?511sN4`6sqwE%H1pS1?e*4a4L6noRf|UfaV`FbzmK2cPPkzZ-X}qB2QRieH;fO_Mt^)3+Ee?OUkF|d%AoVK zejyp_6!A@j;5P+;D>5?G=Y5K>VK8t`3U!iUd4B!)t`LfNFwTzxMXd#F&H>=ChAQsw zc8>Pk7(J8Ya1)h#_rKrIhlz9x4&%iifE#Q16jD(O>xWR&DYSXt3#&KdYOKuDMuN7| zabvCj+LJkLx|@)2*_i%&nA0vS?Au<~?pf1%O#v&gl#M$_Y=B{`ky36k3{Q*&RwVU>u_j& z^=Vp#Ew9oCqvJ0GY{FVM&ARoLCX-@aMr+5s0m#Cgl{5tHrQ}s~9m$TuX!#JT3_wII zfKwoV=irg}PbD!KMMj3i!zZx_L9*+rQydSCFs3%SwpX!97V^t zW@clf1YEI=Uj4rWV08+fIftxz-(*ftjSzSZITBu~;XwuM$SCl|h=l=;tgxdovK3mc zBm3NN8y9&Ip5HxnOuG&W!58w>ml(Sjc9%lz;{F{ zszN)DEO&UVFSh}O_(4Y4;|l^A9u^<6Yt)*;yM5p9VunqD!21%P|7<|%wUZF|$dE{b z3hS`I_iu$&PkFX@2s{!eNoq-NaO+K2nr0m)fOLKn97*Pl+`n%GG%^)0x4uB)w@2TJ zK%v+UDXGXgTjkjaGzLC8AJJXIA7RO6oh@%m2 z=b>3a|9in!RNC+hQBZpH$sra9h)6I5TnDqoTmsSwVjOM>wgGj2D}1Tq%x8YFl)xghm(=tace>Yj6LXoKKKJ;M_QkWcB9LBQ}y7g~)6u9K-|7 zwfTE2@9L)sI?t8r4+gE*=6YWmJ}nuE5VQ{Q)D*nB*U54*_f8?*i14UD2dcvI*R%14 z3KXD40wKjx5WLsA(Du+AQ(jXkcH=X2t2{sO7%are>Vb8Ka_y3zA%dnSTEc29rm$$j5*oU>y_C8xoyS03@|_ z18f`y=;?&)N6;M{ektp2A1GkTNi^tVP73ymc9P=iIWQh9yAC8KSSaX|hPlHaSG_-m zANQ(dF~uvuFjmF?rTgt2>nY*8Loe=K(MuCiO-wYbU;ei-G z&a_;+8ZE>Jg&fE9%^*BQ>EbD%EI8bKA#owIZN#kS>HP0;|;^!e43DUM8`whd6;wE)RrQSoIyo|*F9O_`wjK^OHVTqJHY>Ke*;tM|75e&m?eASyd zJ|&)U*o9y~c`meYla+%>_(3FVV+;m)9=uBuyCteXSsjhO3jtyr5Oj_!8M}F}q2sv~ zMJ*kNC^|y-=l-iXX9uopNl@Y9+qOwfobS!kj#C+P=oAgoDWv~IXnj&&0SQf!2#YFoh! z7td3gC(pd$A2d;`ZFMEeY?I>Gvg!Hb4+cl3>y^4em-;68E{tZ2(K|uZlI8@53L&n5 zDq}{Fvtf)6MeO1=L6sG->I|erCt6os$s8BDm!nLDBU5VmA2~h5&sG)P@gC7m z5{rHQa%nKP2{9 z+c^O`{-~WM#SV>Rho~J3$WFE=JXT9p!Ea8;AqpZHKYK+ugPFvw`vi zj7o=zpj+^$e;j%2+$siC_*zgd4@6992x>AGyP)l?yw*F$_Wa*B>*ca2Y3}h}9I4e7 z2Tn*@)&tSBUd9mC8tH&-q9jHQM6jW*JbP8_XXI2s&lQA{E*72-#aghLj+uJwxuCM8 zuC@M}DK|c?js$u9`}>q~55 z4?#^c(M@7=Wj+_&xpI1&>s(xq!HZD#LwCM808+r8AmdD_tA&dKlkLt`841;85&}t2)=dTDd3}$5C@K_qa-2!_-~v`XXO!~>E<+J5o05Hg-H@m9NZgk{TS4fnpUW3wM zzkBKABFEs0Aok77#$W!011+Li9r2j@w+fuO5X|1;UW;}rH`62#B1+^OHAL-cE-9r3 zs`Y70Q%W}!g6Z$O-r}auIC;M&`;ejT0T&T#W$B?t2O0CYu(2L`Si!A#UJQ8IleAb- zxi;D$*0Nv}=!{a9)vB8TyF(;-%HE|)zDv~oo6Ez(OsyQC$}Ko{fBJ0z03ZNKL_t)~ zwAai{p_Jgxf~~f!l=`LWewd~S_8RaaVpvxa7`{6cgrUuq1o?084fIa&@fn47w4+ly zbzsyI#!}~_-va~MVu>=lFu0roK&V9l&HF!{D)<&>roB`OK9_ljI{eqc>^9_(d_R{k z>Z{1SlKi9fkIwBb4DmwTle&LYwl|H73d(s*sO7VH|KwszF)wcP3p7qUJAB<9%rZx{ z53hXIk?piCr_h|ET%T2b{nMhkoDBaGp71y^>46W3PQjEONsg$9EiVVutbLjh>``94s z6gl!P!tOd^&@y_d6eR6SM}-U)Tns3+!5*TL!V69K5L|l0?PL(FTb}E7MK3#2q+gY# zaJk+VkkX5ZK`ciKWP}a}><*XEq4FNJ*lE$Q+8Ma9OO{?oVYEMo=u4*caujpUDSzaz z_E5)ydknHyD=O?QcPnJY`@lr!9)LyHI*obB2v{u9>jqRKyvdR!7f0HH#aAsdW{vIo5Fnx-5lN z>w;qpTfcwq<0)0~Z!2s{DY>UkAxOs!VSm;RoG%%Wg)QTvmIz`4dYK5*Ab$-u&`$4g zp)Bh5q!vFnh?GZF%>4WOq+GcA)D*h_zTw{Qx|CTM)O4fE>tdP*rFVUR6qTti^0-jb zshmos@_I^(#MMDb+sko|9;B$#K|Timp?W^-RfX|{D)ZPRr49r|YM2i85fm6wTJKtA zaiLOpK6qI*couLgqGEz-=Yr;0=xU&hjU4~7)_br`YuFO-Lcaqr;u$!3O(laMdk(2} z@?l2bdvr$__MTW`Cv_aHU-?B%a=8p7s%aEC)-^e@@I+&7=rRURyEH2HYC?GG>!|8S zqg?7vhc3zrQZZ=$HhyDQh^nxzNl!UiOX?OCNs7z7jt!vQNFd@#JF9MOqp(t5HRV~jWtBqqWB8oC4V17_ zw|2T_L~rp4zj_5*U5_BX)sk@rw8tuXDc#?X2RZ``dotJZe(VKy?a}un)Up>p>KT2(Nl!s_PFNLISoIU;@~owoRFZ{h$RTn!iJ(u?oyRoQ?g!->Soj)u%Y zq~kggGZ`uBK2g32?0=s)G;TNtx|G)x8@gfovx~VkD`Y_6VuKjg^lz;Ci~eLA=NaF= zbB7bW2H?4}GRodJvGwU6_uT88;JqFc3xANfo-|}RJ~>61{_Wwuu~OarP2GIQ;daZ| z6;g!33wb9l;x(jtiIvi#xi;bJr|0C~hKUowqd4~c&Un_~d-kC1><8zn08^g=S={yU z&g-ZD-(OM%FI^sZs;x_kIR1Xu!}^|2c==7N+NSvqm=&(f1V)L&cb+5edYroeaJGSJ zp7*V5YIWUE_jlQ=?DIo<7PiZUUmVqZKfIr3O^z6+Pv_3!yL?LH(!BYTTn_u>gERZ$ zjitR_RZ1AM?>*pYPCiqHunxSJcJL6dEP=y4&Fh|mI0q1GwVrbv)|Ugm0aN#Z9C`dwxEy z<{aHf7lQOxJVPvd+&8xjgp9!-<;0yTX%#;(YOLmk5agPpm`tRee8hIa*g_I2dKM)- zV`~XO*xw-zjiQ`HRb2>NA{!`(u*989(?uoohDO3|_zy8iP%(H3PBYI9J2J2yEhTKk zxHdY3xXZ!UYXA~lW3+fhEl9*=K|Bay9V>1;Qi2?=)+4UhM{qBrYYls?);>rGhXX}u z=u}@qG7SfCCFcc{BJvbIlJyV#+^FzgR3xtvN)N`edu|DOzuO~Q(Fl@O1Mk%g!j1@% z@A%GV2vU3%#-i51V6457dy4{0&N*maKEa(85^`wDnN>*2ZP`M&2cM-h?_@mYXSWey zZgH;nrhtyqnGEIpAEN~|5ER!iIRI8EluJA0_Zk=Spq#l{#KHSM0v0;s4F$bW(!7X! zA)Z?{SmVBifU@ucnz!Y^V_Y<6D>*ZCo*FKCu3|<>cZB;zHpoRX>wPATebFAUwci6u zM(My{dMBlS-g#PIneMWP1olT>&?0vf>d=}+*y9Xn%uv?cqr%@=DFscjxdO!CMSy7n zd7^**VWFd$(X@FV30Q zd3OIHgsbk^{ia*D?(Z>F4B5Wij~7kSy51`(vMx+tA_q|k3mNCaC{g<^_Ux{>K4|gh zsyyf%z7qBKan9TGA&iHKRSJW07binN3F|qNMo@5Cj1Abol|s$QIwgw>a`X6D&{|3) z$!p4HM+gj|I9z6G=C zNawAV)w{4b+6I3}FRah$doH(RE@jsTCWQds23i{DiMp5Ot-=^9gs{*Wz>ucX+!ziG z+=MjeWPs`9rInse_cJn}iI#+{TXhIaw0SyHOHL`E)#Mms81R&eV!k#tV+x!;Abe@d}8$IKT5l5pWmORi#sJ9iEIL6ZOmSoj{?Z=hG$1K&un@U_&}(fs_Ex z%#rmwrmllvLViB~&qtwZmCK#>B#gM}k@(j4Zz4FGi=1;k*O3x7oykD`VzvrLd?evQ zotrSmRlR8x8bCqqiqQxn5W2*tv0-1r7!w#G&s$Z1dyLum%h&olC-J%a9tdl}r6u;V z&u*4lB!aiP&PZeHLFje{%*M|?L#x<42v znNW_2^un6{m1wk!h8Ry}H!w{f$Er!D|U{xvVE z)I|NOYB+kXT;S50Cp}gx{#9FxCJlK;2~oUQXG+_oY-E|}z^K26tf1L{L5}?cQi43s zUC1Yv8I zqvZy!6cmma6UcHX&>9)Yh|{M2O$!U>I>j?$fEbzk1T{T4DQJ#Zqpctm5!(NnE`-R( zQ*poZ29FHXp?L%ObZ$Q}(piW|I9#MFy)5K+Xpz51@t?q!3-F`I3*#tsMA>S1h8`~g zuL~;>EQ#w1$9jUk%7RrpMASc$hwjjX^lC*#Wi5Y+87?S+Pc9-76e0fxRw+7s7wCud z@5em{B~VsiQa7y_b1j+|`U-LO48Chni%jDE_lz~P-!cNIHHn>6jZHj9PdtTrcUahk zY#N}-Lc%(v^X`S`5(@=6HSy$yXCgEt6c|1Rn2VrO3k6O;!#NNxoC)1vnRK?MICPZ( zpixCpa^49X3f0Kya_Zhja1w)OF3O|#<$EtN)`+o@?&Xy|R7ed>1|=(T^i><*-(uh* z;kiXEs{IQji7vE~F>iZfb#nib3D)3Ho$wAAC4j=b&jK>v$-2w;`NM0XQsi$q$uxYY z3DOmGd;lp&JLib{YGSg9g7x@+xXj~xEVyv(+AzOwcx(l0_w`P80XJwpJQQ-PoS3Qx zsOy{q{&gKh^BM~hGy*p5x7X!#xkfKHKvxsw4H;rF^D8#JQx7uCDtOAO=~=*1&EOJP z!CjFThgSu!O<_i^l6NL`E90YcX=e6+1jBQomL0Sa%eXdA=MFmwLRlyGs_8s=&*TyW zp$g&BvXWl}k-da$dAdtRo-z?h7pT;@db-+nIQC~|M$Ek{>ivaDyS;i8Hur({jh zhAy_z*5G8(DNB+=YZ0X_&kGc`Hhefl0{T{3l&5*Fh7k(!lphZ+WOzlfNt4Tm9_g$d zJcgI@X};Kn^5NdzOqN$AVU$IaV{?j0cw=y@g+fb4)iSE8dZU()oJ07EkSd(TiS$a| zQ_Z2`M4#T-&dO=}-i6aJ%0Q#WrxF&d_X4b~cn5}eD&B3?R9M*w;0_-AFh@wS;>$Tz z_0A$jZwJk!ME)AyLeaZ zf2?+H#^+rnGzNp_h{kmxEO0LrE)0aTr*Sj?1KZ=<&N-oeb$fPYkMlUTZr}Joaf2Z} zb*s>B*6a4XM3lwT+N6Ewbzk{mZ}1uUb-$twAI~zw^AS1jX-?hYAI{3ZtfMkSZVJvi z8D*qb`+b4&e4WOnfPqwZls6Mnx(9S;kBl-Ge0D+R(^-?P?tuhSE1nAF#wEBu`i$#%JbuldT1G8s~`*Jn0JCth-i&wX82FcVFt)?-t%Uu+Rf5u z@tWM~S^-x)cW1UjIHHCTYMGI@q3+4Qi$E-Ce$qV($Vc1f?BBxp#h%l*?x}5c`#{yb z9P%&DoQ{Qk&$_>0kNI!4j^F3gILh}FaC^oS>ixdRnV}OQ*2cNJ%?3^j%C*rO_q{yK ziwp=p<9N^sojN4GLj}Lj8wvP6Cok)}nD6iWi~8!GYJV(VpA6M^PTprGgvL0p;e8>T z9E5kxI05x@d!8NULhU_i_)(4HCzty9x4Z8n5$E##E^f~HZgE{&3=^Y*r4{N&=sqhf z;BG;5FVQ`OLP|bUf3}gMqQH*H;t1HF1YA_KRGqWY{30ywwn`F06qOotccQEUdIZ%H zf(@S3Kt{##x1>`Rs@bP^AFRY0L^F_w)*~}1;1LrrxoFzTF_!PAIewM733RkD z450XLi-y&=pgw_>1avG$h2#53@Q;thw;lz*cE4mb0{Ff8nF5io{LT`LD}o<@F>EUU zV-!RwRj+~(T}E}e8nLV8O|ly&r@JW1)KcL<6%DwdOHAMb^b zZIh9U?*o#Or+GV#;rW3pqlrCSu_&J4rG0lm)0`L?e5Tgk zeD+4F{BNy=T(lo1X15Q-s>b!X%Uf*r^k^1uD+$QY)FGQhR(eeF&YssEtWsu`DMNZWp^u&Bm zZtVNEntpX;I^W}Ze6jssMe1ZI=Y~-ighc!vfy;B(k|4KzB@Tk{b_)(K^Bu*H=Lj<+@^rfn?LI+5B??SkUS6jepceOT^r zuJf_^4vpGF6SBh7ivBtj3$o$-m0#WUQkLg-Ep}9DpxGz0+O=I#7I_cTe+t zk#9wlcaMZpeqRORPTYr6Zko{sDm$dgS~9guSG1&QQY)EiM1EMNDOVM3>#~kH5@TJD zB7Ut+vq>(bkyXyWF}v=1F0#m-TGR5JZrl$vuudzO%F#4RXAX58oy>9*wAZ-~NbOyW z6(ndd5WPK7Tr;sHbwwwM5Lq#dr&wy8uq>XIRB!fvg;Qe}!AEo&OJ^pG|<%O_LpdGvUCnEiUcZ?F4MiH#5- zBDw$9KtYO{bDYT9+mK{4*+qaIr9| z?KK}QB9qB;(^wNU>|$@6IuDFJW&l0Ri{{4KzLR|~EYPbC+oHS{Phm6F7kxWwFI^g! z!l|5C>)#E=3^ZyP}AjjF3P~gVGyz(sOT^a5&1BJOb?ifm5Js~pA zT3_Pzr_C_Z7^CdNP0!VcsIYXrZww8{d>*4)R z&BEqZbSuc(vGn>P{0_h5lQ3g-ebhR7w*WET+#+u&T7$>;`$mIM>r5*`g0bID?%u= zeJcb}-j3)N-o74mYTOePz?Sx29~xDP<$``VbU>h-nJCIdd`4x_xC|>g(Z9dPrt0~I zNgaEq+sw^Lf>13z#n2-4ZTzEYV7xGNt#a9VcpF1gBm22HRK_K_;jFgv1Nxh}{k^9g z;X3l18&i{K3Ygu+ODxFA zMu11+H;hb!R?2EkpfBq!21IH|N?^#2j3M|mkM?YBJwn%TL~HciuyMuGpz`@m>JT5o zOps%6^?K|ggC}@twO^E?Z8ssh|G`d?* zaJX&zPz1MS?_j%U$zU&Z^Aic%?%FQfL8YL3?sO}758#A!@L{?#Tz?M{>@xzMpizX2 z>P5CtVi2MLJy64edtoYw(=W?);j+82bzoCQQ3Jk0mciLFpfaT7-EBR`qHemlpvTAQ z5HJ(o$}Rw2Gf+rxh%e~hXTuJzp^lm}5`ME%i1WTQd50?0Abo?-W}*QaB|Z5@p9g}_ zrCAIacm}PT|+nc5>I`#-`DL_$R4z*DS`H*8_2HN zKb;g*decT96&yw9YrE4HkoGF3X#Ef-uxHzRcZo1(LhT)pG#Pxn6ZWj= z{Zn+;e5tBu+Wl4gZ_kL9VZb0Sa}pfvB#-rXzS z0R0)SBa5MDla*j(0@@-h&Hu2xFXkNU->Vw?u1R6H?^@ojvJ1VUe9%@!^_%Y-k8uMS zveA95)T3FurXd`9ZcrR;W#bsFq>ys04` z$`e-cHU1~~lS9_(#wpk~sJ~6TgKaw9dWhiLdx)-DD%TB-l9SE|q3?D#K_-N8PgAzn zpv$l|0yd=`&{h9#X^a3fFY115XbRWBwy&4oTWg%4yWgV|3U;62>|YbC=`@;A2x`r6 z@9NxIJUtG1KOc&rv)8&<&yUW!Hp6tWz%<6PM)!9#`@OU|8`Pyu%00g}I9&}lWQdiI zY~aQ4i#C8=`XGqNM%NQf`=-+<{2Podl5|o)JN`C?jGJ+N_d~b_YkPKnz`_MLI zbQfNjaqM1kt443+<6`v3x+jg%fR-O_rsI^ufK8`X#0ZBre;~fVD7Tk5owi(K0|7ft}X_?0%7<`L^dH`XB9V@t?X^LEgpy__^!XR0Bccqf9C}n z8uJHOt3k%pNN(t3!<|6W`(xI}9l$IYym(hVcte3*c9?+Uob)^#%=RH%saMy z#ESH!-OEja2iw?t6V{-UUB0x2!{NOF6CHZ>^NKZBIs=P)@TrZB78dTrPO z^iZ9D7~awCG)Twz2{8Y++=z{zHd)KYTl6moUD(hPO^3;54_i40BFXn~vf9lNK%bk? z0ui|~z~K8*hX10zKyC#mK471M)IXJ&&$inh{2ey$vtY14e|N8}W&g`%kuw&>P1&#fVQ8Os>GZ17x8(#{7Y)^RADs1@z z?CvqmdYarH@joL$hdMujNAju)&p7Nro`U>?`}qH0y5Z@&G}j}v#iZ%PcCDhd^oZnE z6c09gWs}HkE(uUO3z6*FoStYt|6i8FBYvd-*&8;<_MRWhbkcgBqW!R*`JIHWKXyQbRz{SSl)0uey03ZNKL_t(mu)3{+N1Nvgep5cO z1T^;AY6b=oMhY3dfT9cpw8*$I$f%QEl=M<4BXbk-l2*$apI_*Ayn_UmK!JWT=-UGn z<~cApO9oQ(4lkKDYK|3UCDT`GaO%J@%P-7%r~_z<&Yh73OLT^8IFN&E>!f@wqjB2) zyIOYGe!eeA@~RD*>#vNo zKytyApd{1tMl|$VZ}gEBFIaqb`^AtEsVOHRrbHq0Wq6vrQ_a6D0hFu zoZv0qP{pf}kGh(Ta1SDMF)!>?p`#4WsMi5%HkHX)uj=WsLU)nb?^9&*jLj-fDJZ}Z zrow1%Msm=2Ery1V<@F_@REiGQmw%vQyj0Fa#jqL4&sc6S?D!}$)sdAP@x0VJd}i`K zs5B{cr}h7hscxoK!?SvRoL^FISe8Xd<`zj4V)Ymc4TSF2t*A#3bkczhgs%}MT?%ar z|N02zkX=ft%D>fJ8uWJ^2unSd>UUz&D|G-#9vK;nf%okXukNW})C|26Ce^pdNn`Gf zZJ04zy0MG5ZNVWAF4=~T+nxw^fBKYVf36W6vTDxQ@P^Kp?FILiu1k0CRd+Qtw=RQ5 zEd25VCYg--ucP4yjo01Mq)qwKoz(1^@Ke>%rA+&2)c!elQkc~{*}P+0ul>D(U}f8X zLg}X|u ze-_a3RnJFuZJuuGFGMS}x21Onb3?PYzMO3Jsx-Po%e9|>FJ^D79I`#9Hnu-}K0~z-x<6^ITVAEOEi{~)R^U2j)9BBGfAxrtH-vqhb&w2!7t^Wp>C-wY*jE^&T>4@LO@O~t7JoxC`qtlEBI%lpmlEgFz>uJ03 zg(o8)=Ssa79)IXP@niu^C~|EUsxLS2Qk{HxXfF%>I--k8Lvz}70P!sw9&QfXJ!;=! z3=x$-qxY--jvu1&&nF$aEY5uFrNN8jK^Z&Cn!FvIQGIzyQq*A2tO#yU<$?4v%^oTC zk-}(v16zehcXw(_w@?0ycux0MO7NztYF8CX;$2|7q*d`z&}E1~a@ zCgqa`D0&SHq4P;wm)a@<#7~c1jQ^~2T{f7aKo=luiu9e#QJOj&N}OTCK$JUs1~+zb zPraw793CYz{Tv~V{{k&spN7R0thX)F2I))+{bK6XXvTi@NQA?$I#ZMey^6hc+zCM~ zy_O)0wLRm2^=iB z@*ldX*L_m^X<`#9p~CCT6Uw#D(7orDTVC?wg*wF^62xhnIXYeNOlbhS`zrf#AEImO zu&8Tkv-i?t@HT1dY;k&ZtlcI_&`TIlGHkJ-VI!9`?Khkgd&c2sM z25kVmvW?X+N1F`^Pi85(;=-7&+31^|Sw!y5fE;_ugL%qsv&KgN8MWU@fUwh;6MBqE zFXb}D%`}ABKBs$Vv9G-w#LBgCK})kB$c+fn%nHM-gu?C-d0;~4^ z9{^#yxVr)7F5t&!#VE;$UnxC?1EX?}DDVxJrttO>3BG5RWu~(T8Jwr^d!ytC7(sK z&g|-IX8iE!+7E2712y;FnJbCkbWV*n=+|tP*LBFTB9`gX~&mWl>+G5~69ia`w zmyc%ETP-m<42rI%SCw*5cXPL$xEv+Dj;S(O2_M>$XkC07C!TORW_dk(J)Tk}JC<49 zoIvLi809WCc@9BsY2PlsUz6<<)8iFAz5~Z$#7BLrlQhxGS}T9L8gYBJERS0kYh<8p z*Br9V^Vp`>y8mo`fYZtXEhL?9wAx$*Hd&%<+E#C|L*=?0d$W+B==p-^bFjqC@qtbN>T)Uw zO-tPpUd7yDbFA94VQH(d9yfP6{5^)DLF>}*pdU@h`a%0Us^$8gJ{LpNX>rcAh6P-7 z+w;4Kfl=jtzMEcu58vpuU2o;Z++r(mV$w+q1xS;p;D@nHws%l< zv&BzAKSu^ulXSTe+q^ShLEqcKwoPv1)O0W*>8W$|Zw7yJ;;X#dk_!X6L^1Oye(N2% zp1J*e0fhN1c0Owu(8(dObEd&I?SBU+;g9`Y+peJl!>|#ew&UZY+!8@jDc1YFHT9H2 z?Tk05*0^x;g`$`_n*H>?XB}@1hFR*MbNDBOEBK+xqDu8(qqvX!tJMj^fG;;gWLeO$ zV4J72S$L1%+gz45FDuJ2YICS)@>DEhkdjF^EZACn^$no%k{LJ(l}+FWPQZi>aa*T6 z&F3Kx9DJx!l2%oq`LF$nVn7hx)h^9j?DqG^3~Cjt?btP0wH?Uy@yr_G(k5|jS8TQF z*68RdBi$Hu(iAj}+iz?cuO6ja@q%^+uzb{F9IT2~VJGM{qwDTyr?bX+v6ThOPaS}BQe+(Fc8SV}v%xHWns zNVRf5FMX7405XRtiUEVr=!Vgd2I&x7T;Ye68ApG;6mxVOUI28vDRgM9LaiDQ$$?Zn zv4UWW;rx zmgETctA?xCgWIP>oga`KXAJ%YcU}_mhnIKhCM`9407orvB3U{E3*o%&`S5twR#de6 zO0%mlW^7(dqPqe3y<6d)AaT|YlVF3;QPiJ9c4rM;EJ=*YKyY@vvqtxnBE@3x4Pf7; zMk5)RaBdvtZiFv=SwmMIA?bVA=WxQn2E)O!h-GB&kwf6AL2%kz`BkZ}Um*h}hd8Bw zRb(H_&I|{-+JF}?0^BNAkVObtlzT@nCM6GH4 zH@wS6XczdtEmcr*{do@=YoG9yK7zg^SkXmayNm0D7TKO0&&~- z9(`V9EqGqnspqW6SvChEJ0A^o%uvncF&5;%W_(e2sqR=x=sx;Wu$KW?=|n({)zr4J z?TupB5G0l1Q4`qg-_PLdJg8|ds+Y!t9}F#;-`jby>;JLwW11O$BA9Q`B#r|_m zPs0llk2iCq=sL;A-u`3Cr(7@WZTB*#$GznX$bZyyQR}vQ)X+lb|2+0)Y576SIbHh3 zpAG$;CSA?hUDVm#)<^3kYu>x4HJiV$JO=tmm+KaK=SS~@<1|@;-D&z`XnLyetTO3Q z25tHJ$N!|=c^Z^r-CK~hH;#1Ynzr|c;v06Z)Af6ci1454{h@e9pP@yzxqIgh!8#h9 zK>pK-Af`r;ok`j8DX=<1G*5f{W@V1q?^Tvdlf<>o$=6ZpY_EH>_BVtY=bvtfyBPkn z4JWl6q4YA>%E?gwZZYcrXmqDOfB2`)nE4!Tiyvn)$WuM^tP13;2O6s5A_O7IA5;Rg z9ve-$3>?25Wm%gZH9uP;w(b5Vre@zyMzGz6)MxmBP_#)3#L9@U8GZ;dsJocA$(r^G zziH@6d+4Rfc=sB;fo;kb^?NkUsTH30UPs6--g`kb0>Ub0Pau2$hTAaAqb*?eyx|GG zK6HV}Wl(+MI-s;tFkMnsn%wu~KbsZYb*qt0NOj=po<~QYhx@-xR}dXcg+~i<&SBut zLDvY)GX}-Q2)FMxZX2}9(%U;Ddo+(RIQLctc&^)qXYKMSm8Bu2}s?)+xW7dshb zc(vsXAHT$viSg%}V0N=#i^CWF&p_hfqa!m^gS@*OAFpkuA&ukV$6Pf^_X@$@+ z$i}?vX4l9vs&aLTbTLgAIO?&3uu(sleOgobWJ@iA}4HWbDV;936;;-|;i5DpS& zHS`TA=mz1P5CL^v5A6D)n17E@K7y29sYg+{0%AfW_C)!~YWgIL;gc9dHjdi2`($I~ zUSJ4mV4^>}Z7;qkhsQ$4^tnG8PesCfnkJ&mKBo$;*QmRqLm^YCjIY?>5Zqfd-&y0yu<>$o-#gN`oggIH~tMNHnA&2kZmpuP3G{&fKf zcG+rete;(mmY+%F@(s|+5406BaDvjy4LVy9Nldp_&Fp@LqvHFRC`_7-pPYkE2raIM z;=eHdr+%QK_r1t- zX2dm#eF2}PyQy0ftl5Cz%?P0z6BTHd(31Cbaks3Dr`1k+19HQvLC!J{nqvYHx97VE}%HC~}N#-O^17dWA z*GUG6DWJRjEMcySgr{%YlmAA&P^e>haVAuf{nN=3IL5p#aW*>owxxe|eaO+5vo%;E z>XrlcxF2rMl*RNUmv$_`$BBVuBsjP+PKjRB+ER<eJqo++6$wYwMGu8kBRGOaE2w5D|OrrZ-0VjM&ujrMSwCeu+XO(1p)+rPm& zJ+n}j*(Z%jj^G{5znw-^cFg`NApTkhU|Qm&iBMu(1?!Qq2XGniSodnDZD=FJpe{KI z>DFuCYLy3)Ug0S|huHoKW-ln|wX9iJ+#1-@p~D_>&&cX8=z#z{P`C~(Q-KS5rCk=F zC~C(IQ@P;nW7J|-3wL-K`*eUm9p93>M~nD<;&lN%UC_{w7Z&tJG{h+Zmfi@7bV{c) zL|g=1uVw-boOviW5VjppbOJLknW53bp5E@fy*)91gVtxhTy4rZVRJ=1B>l!9{(%Ap zND?p&b?(0-RYx?SpFHoKiXI;pk!5SbMgg!;wPRcR3z%^qb5N6cd>>>B`>sCw_`3#5 zf{uZx&+6Yp|IMt~HnMumW#f!?jIb6Jrtt;a7yg)dKXZTl+hnRrGat1F%~}-=)%HTq zd!@TTalp^U1|8@k)_;@`4GVO&zt=)Q|hr%>^|u^sXbr!+Q0z7j;WY;rR_@7 zJ1Dl=W72d=G&H_rXRLee%%{nB(4cT_q_L$96c03K;K%7qigZa~(EFy|rMn&idQobx zV*^r}v*GNI9ltrJ_AHpazwgCAnlq{CE}A~F{=aMHwC<^z2Zn}vPnv*$ zqOay@P3I7A<00>~?6P!o=qZtW(q~csy!jTpb-Lfte4^!*=3^}c4NuVF*+yP#y@AD* zLt@UDK0Efzfoz;c&H27>v&TDYgQEv+v~Ezew(`P`kG>IUGhcJnLPrxLFKJ}~9N;OV z_qsCyr@@g01-0@~(^s72c>JIL`~UvGCybTSbDKIvzqg-plwtT;t2m5HcGPPfy?zZ7 zn_OR>qwJzy8j-J48&B4FunN^%fAcQGux^uwjQpNrY=R2oy`>RE?DbLFJAIX%$=-=l z3{H=`_E1KKlOy111y;n<6<+o}%i`w-d`^d6={OFPXEpI~lrjTjKRa_te|5q!Z2M^S zm}h5r(=$$(>|=EHBAMYD^?jFVc1Eo+7}E3U@WI_D&Pwl{--7LqVyG_O{Q zG)`5xhZ(j_X?s&bu34EQ(NLvDtl3Kycm)gnGMYl3s`IP zu(BRAm+fbY^&%|E9`wmC_GJ!j03_&a%}vVzWI=b^3=)!ToFFs|BcsugN0QyD9NK#g z3q9rK(%345h96+jf-|K?G%Y^6cnPxmQpi7ve3OBUHr``5Adi78s>oh@hxX2jNZ;_0 zPWVp(1>KC>Hl$pgC(#=0-e%xMrKC#)ddB>JbncuZ7Si6m7_6Ot6}=xoK*7Ow|347^ z=}md>XGRKm#FQv_5~Si`$b^H$ErbQ*_QyGz89PI01gBYHQc4e?f&%?BdLr&okat!q z3K16nrs=u_N&^$TyT}MeG^cs31$JJGgW|by(8-8_vB-FT;gxGf3PLA9bbEw8Htn53 z6nFx`KC-YU-lbx3*0v02%iw4iM(0sD2pponl3bHk$jjbzj^an?L;p`(u@-6l5BYp14=-G?JD2(DX zvkI_Yq^TBfK1^TcEKgjDsz$#jM>uIb&eD=?htvS%Scaa4B;=Ip3VmrBg>sSbQe*J5 zH+B-k!n;%``Lm#t7ce0*^}KHDUnU zG~qq*N)l1on}DsOPEDB ze#YnBw&}FWz@sC%9CJNxOTqi&{~KkaX88Mf4}+54x_3HTPk$a*GFqE`V{)6aqU zj6*p{6C@X@Cv#SXF$z4wJVe0x+cCX&qvAE-z|G0JurKkju4&T14xZPpDr?4nLwR4T z#uS6rZWR}NOJhE5{A5P!QZP=VT#?MP;)OY!z2CrDiLz3y8kUuCZriIe-izw~SyCsT zN2Xsm@L+j{r=m(!wg*<90&*&ZtkevXbWyXOx@liW?KpZZ=mof ziIAv{=(@-}dRf&=lzCf5JWB^0HOn>V#%9d3CbHG3{|{l&5#_IlYQo@C0+$vz)h z+SO#Hs=Kjz>&RTfW8pc@UC6BFM4=$K2@wM&vd2=MVZKP<>|xfCz{wQ^hsl+*F3YM% zqLRlzV_`>YMj-SXHz#s>nOh~Rdr%2GRXsUUq-o{}iW22G;TfSv0*;OUQSJhQWqSDx zRa(iDpQ9t3E3xj1p;b+ab^gYOLB!nXK*nRKCr4ondL9TVgdd)fNi#{l($U4oZ;v`^ zROJ|oGSKgg5Qk!2o@CDhi$cwnwiiG?*=iUdVot9{cyu!x85&FSj_l?+bNd;&??-|e z{l$LBDEjBB5tAnJA&&ixJQLNhKos{~f|gJyxRR^PUUePsDd`!#w{q*q1b~q{q4#H0 zg%T;_XunZB*O_rJ<2mx_JqWHQW9W*TyqS?-GmQz)LM)qDl3h!3bIm?8dturULZO76 zHs+La&>=FGQ_L=P9LmR$GEHKjt{DxdH!e;l*utsMn?#lS)-0imFi4m!1-;a{MA_G){n@(5{o|M02sGZKyuf%Wk4SR;2C7+1Oc;IwfwR~psz&T5;ZDC=)j2VOBGQJ7Wch4J6V zz>9jP{nDR{>W9GSwp0f{hIVif1jWewU+NdrU5KS!=>eo^_TM8QShw|93+ z(w6}s*J>zWy1aA1*ZQYNbH8E3GKvTuA%H5Qv$%O#l7bXnSP_6nMz~RCapoSX2pbyV zHB9IWyquK`-@y$TQ5`7;7Gp+}EaP`1uY@@ zHWT*1*nVl;U7pLDllvu7&-V6=yd`<>EU-w$9n?g^%?LRge@V z@MuuR3Br+avy8kG9OoWXy*x9&W1E}godbq)AviUhoJTbrBLxp>HMB7ZW7#ZtHtjCM z^{^z9ka*-~oESSQFU&0PvQUCA#F5;1JwuoSfgaPF zW%yOM6*HqfW%*{48J7OOEW2*E+4!yk;C{YcoRC8@7E^vlCyNJXQqdlDm#jT}CCvq-ov`bI$_$oAdU<#f7&GJwAgz5i=!(d%V(6 z28)p{G;&h?%&r}_H9}&~*ithHQSdT*H1jLJ*yP#XumFWyPvxe(_x2S1q3Th{ZcUjB zbS5S`s{m$P=H48$B$CVmO>2ia=QWtw=hy?o%oU4jjL-xBk-}}>*)KwZ0H=GOZt1#& z-k>~NeHj?E2yVx7H>SM)w|dir_= z7~~~jByUEr%_Es;eF_z921=Z)&J-<4000+xNkl+f%Z<;aFU6$Lg(df|*) zf5ufR6MrA&5#*L+U3peqgH^yK4h$uR@V1=FL8xRVFGeVj%Fad!Lhx^>C14wI1ofT} z6b7036+oP7oZR)svEH*m24`*@W@315bAXyHA#B+QhGOy;pN4ELoTIATm_@gYE&}cx z*91Hw@vPE6Hy9f5%fO438H`8CtANf33<-`={Kln$)|eD&OGPsTMhWN@c}WdT;TYl^ zd2u1LrJS7LeT>h>ZTU$8tY&a#0=#;=oO8KzmrH&|*;l;@!S^UKhA^%O=#i^2^#U-i z4fnohZwHWCJ+~D0`G~#W`_8HnQ=#!FhlBN7K4%1Lh}>s{!Gd$l8;XQ)Q|*^bXl0hBHR>~8fK%AbU8%#>h9ALa&7 zI#Qq+9Q$mi(~^8CfU9}MTGd`0{)HE%>XfR?g2n+zjI7;BX4S(UTXcE%su9vywN^ZH zHzOx9GAl82Cs#ZP<-{H2x6Nvq=fDlcN~gMtr>2tMG4dddAbPXB0t7duicwODBQo&b zRsm;hOcaIF=I|~#Va$&Vzh)1S(_SR+moPFe2Yini0bVnL$Z^OLsbSLbKI@)?k1o!B96H=K`?8*b;u&CeDHzGH zjPGy{0km;g9zn^DdO*)1jnxGMc8pmuYATIb_SCOe-!r;Z$Urg9G##3S z?%ZQQjsRx@o@FGQ>lyG&->GlTvx%Rs5=X6~#oM?0|FO5->kK-kC$L0nAUAt5K65lXKt*c?AHk_L5nItx^Z1fo~*}&u9e6)U5@DUFou_^OjcvG5lLsy!TI4xkMI!!$Sn5A#aYR;AGwQW6+jvvExxDX zsHrLy4qT$%p^YrJ3cP23I8^Ygc--bdqP#{&QjjCL`qoKVN76 zd0W1%lK`uVy{cf#U5jdRDn*H_Fv^Tqf&~jP%V3@HW@HCK98jJSuu|swuhtA_Armu8GSWdb#$y8j)Gix^Yh6q&eu6g+I**VDvp z#%IpE!BX-zz%4B?lyjLPS3HeTUew_7!%T(@J<^Q5T?GVEcxZuL7{~$vWv-eE`jZTV z`?HCAGcM;*vI_==M|B?6y39%wAIEcmh-sICHz6~O7TJ%;IO85Zs^i+DHXVgSY&#K> zD3#bqa?7r?XHDz$kqcjmGo(|cjft~0GIE0VGCNy)Dk@rdRiui3l+-HR%FsN|TV<|U zBc1^LkTd6{(sp-r*-}cs@o!W^iJ6g<82^0S8a;!18OXb+Sya?DGtH`(4I!`{521X8 zgtvfcIX1im_sxyK!c9Lku0RvM#4vv*tIBfs`%wk3M+tMs=V$Tfs~M}_fv@y6kC=5S zdI&thKT^1}IM7JEq@(ZTmh*gqW+pr;<(^4GQ?)vRn3(UP;6$aMV5FcRq$s$n-Y(B; zkcCM`H~q@rI{XNxEtEO(=*$AMc+TY|I9o#nj(r!6ip(3khikGaGcq{{1GET*NDZZt zr*77$$K6VsDl06!qY}1a=p}-e@aN9Wh5#69;VX4X`>k#cxkrP12jms;u;-Pm~&~9))I}h2FwQyfIY-K#?4UE;E9j>E)e# z9y5H%F$_w2$Sb9gqcXI_Kw{JvAY4D^&qTFLSR$}e*KQRjqWzt|wK2WDc>UTd*atE& zqGk<2OA`i$cw{`LJdR;M21d}jvx&GBIY(#jXZpUBV9?pR$R+gPw6HrgUWBYYxyU$) zdA7SH5|6?@^w>U-g&-P`>Msm0QhgQIEo|WW-4CK2FB&pOxw{LDHALpj`iRfgzmMGK zRgff1(4?X;MQVtC9;W^1#vA7~U>cOe3gOfx&msSyDmW;4EmYimvDM6O=mzqxaqgJI+^)w~t80=637~WJfLnlMCl)%H`(v-2GxyZOa zT?eUi{)c<^D(-m+N&%%PLuYax^cP2$O4o~bw~0O4jryI~{|B`7ok|X3va?m4-JpGP{+uwUc^JU-)tr~;_SZd$gE zGjvIo1#%Q}3crK8pmvlQE8&SuI9G4e@%I=SH6sZ3!Z{oF+Q7Iwph#Z5Q}sqfu;L=a zxx;3NuFIKM}xU`I9g5uGgFK|40e37B#(eX62YR=jwL;RumAcf0K6 z$)Kr=n>>j&0d~e4Xa0>5uZ>EWqx>qTt0OC(8=8XLEhxfl4}s!gpFxGS9%0 z9t1x*m%}-d<4Z;Bs@iDW?3dRL_N*XG1xF_JOp!Z&b&>J zo2ZT#j}n`o$wl$NhzgzT+`JVw3e-FN^gRLvFU-I=MC6I%zylNJU=f;LWN@B=x|~Q` zyx*4Pze}-y;f?*>v%&8q?SOv$zTC?y<-o@7Skg(#}acRZc8A!cj$k*uM zA7F6W({R(}0XX5^m8Y7`!O9Hhp5FKK4WD~E&OmpZjyV|Lsc_6IOZ^Qhv)mK<7=I_g z!(?~EZX8=P=!yQVw`L4gjZg#s>?vgE?x?QvWw6nxm+wyWnsG5+EbtQ(JMz&`$)(ek z1MtMD<%@*ic}+5$dxp!4G!kF%MIJvbv_yE{8Ci}b4_~w&OX-X;rS0SdmLd^&6fZ7^ z?Q2w8t~kPYOJaO6bUdG_XZRhTQokJuAJ*v79_TJ;PK!j&|DFWYw41Z z??_oaZ;)3Nz~8up9zOZAuM(#o?x=0?{g3<}`r-prWR0fyI{xAT$F#@Y!4Xr%vz%UC z51ytRa=%P9Tp+QNAoch>+d{n=i^?$&4Saua<6d8!-q zF4rjE$SZu!K6ZIqIC@)_7t@k2+LxXl9J~V0@D$(48{ECYysxX=ns7FRvxg2imKU2Y zU3`3NujiqjG5eG(dc2GI+bKVo-ouHjIo_~b9)S1V`M>?Gzwx5THxd}19g*+Y79O~4 zylw08Gz;^|W#B`HGnxU%L*NbT;IUuu`t2S%=a&gYge3ygtDrUjaOD8}I#0sc3=y1^ z0zCNepPdJGdH{GTdVVI`Hbv8U$+|yWal^QY22Z^PlwXTEbLV)0cz|((J(SiSfaSl- z0Jv;|-?vC!d;InZ@at*B&yM)T1mw$J$G2}g&p1y$^Rf8Lx8?ExoL45{)U*5aBA*|X zC*U1?dSCI{k$CLDd?|f!xKO}BuYc}_ibK{?e#1oK0FRnGkK1-k49a)S6SaXb_^tic zl>=}pwe>e1K4u@_XKfKm{=wp=(Ke86oUo!&<@sjzD2gQ!% zA7jK~*D}$004^MW>-nn&z-MQDUb^!S(vI(((V5-FCtAx(FrhvV62^DTuQ<$Q0Q@TP z{d*qFY!1${i})_Si(J6hB#zRs85Z@A1538+UCgcLhh^i(ilr!o7>D z&imNB4ZI*HXNtB35DFYdLtGw!>-lX4z}Kwe^ThgVbleG-%&+5rp68_VwO`=d=eO4Y zc;`mc2e~a*65#cG=x+KhfrL{?aGqul_hOZNJ2!MrSf2G16td5G?2PfMbxgaj@xFO? zIKZ>D7C7!xtbCY7PN`Gn@&H`VN0lO9aj;LRgU+KyaO9#-Ru9Xgc7PvStcYO$T0U5V zUkMSt#~7cihBu`IA8X*fquJ53bG;0J9m0YaF{rhn+( z@TDZ?89Dt0aUOnxUta`vgjW!bPp%j7a8K9399CH2dr*$a@8f}NGVnMYf^U+C4K5GB z^_*Nm^7tYDido5beB~_l`#8}|kM*2eb z!dDD=j$8TDM*99Vdz_px_j{S~p)QeDR*(<>#-u-tP?oT+eI74~{s3_|)uAXBh33$KP`*+oY&c=mY0mAFB!8c>aX! z;g*U}#~Yc)SMGr%cwxVeijgr#7nI+6{JUq@i^4mZ7y!ltaG3*NqgSq{N?lfW0Do`q zc*gZ|;%_K_mEW)xJd!rgOZ3llhs?YP?}ZNA@4?`Xzd7ZLFQ)zNzY7Q8dVZ~CGn=}@ zKSfWTc6}4>_)o6lBREO3NrETf@E5}Y&scu%TmK$qeqaVqy&dpn09;D?Bf9q#OToA7 z0X{n9>({v>`Mrlxd9#%??%eu%8V@eS>|FAE`&{^z@D@4^=s`r_#p_P@AA3$@TwUn3ZRv%y+$%yFZDy&7CKwEnhkNPq&De4)eT@Ip2v7 zz!$vj3ISZtm#Q2*KQmsta#niOAXxB`b?`(-W?#FWG(qet?N;UHOqbENP-aUKqKov_MpPrP@L_rN~v3)$5B5=0-l-L4S8^?a+a{W8^kY!WcOlY0EZ z9p>{R^HuwJmi`e>K|=F6zJC+2)N9N0vexK$*;%+XBb@h8>-pMt zynfba%VT)bdgZh9`~+UbA^csn(D0V6w_sPlx*f1h{lJd>|6@IeE>mOXll<*e`270I zZr(S|o$p!|&%<&xgXzUa5sw@7m59K*TQ|R}vchcU?RF&rUe9mQZ*LM(zk+`JEquT? zH{ChnqKxzK8b8V3HT>w8mG|;J{C#{s1U_jjZS=VP_g8>`@J0!6)%gD{!{Zez<@eIz zy-nuu8NT9)d7INJ<3kD$uiFBCV2g`M0*4qhJZ9X}BLU)c108DsT=v2j8F^2~1Sf!N z_&K`0wQ1U`YR~cFc^zq>VaL?=K+W$Y%4$4d@|}Fsz82qoc-jZEJ;5RG3i}`&VSwTa z0bI{-y1~Et#W-dFe))#+-A45jYvNu4;Em+v@x1tw9t2rl0^Y=;l$Y$)u()vNdk-ri z#0%sHPgezSl_dTmb$qS##S{Csoh8q@E%*r0`C)$Jn`X&RCyPE=Ui*st3civC6J;)6 z3kCd}Oa=a8O7YV?abkhZ6YT6Ww^P1hzApoVeHvpdGANxL}BcL?i=W&3pOaJ8nc;&M3`xnVCJD)rQPve6}WIB|K_x7pc z^Zap~o0@Mw<5Pe?uW%mmS-!1L>a|q>Zx+7aD(iVo=-RKhO_pD|p0hMI;)fQ@Gt!&y zpyfUd%TLheiSn1D!wl8&t|Y+gxfJu4YyG*Y;p3C!Z{!<(4{PyFFWp^wcmjCiSI~$v z=kQIyK)2;`!C%kyj1L;VVUj0SJb3HU-tPgxYxkCK-jCyMr1%&upH||-@WydyT0EEr zRV;>I-~qTSn6JIM%6Ipyf5+T-cD8?Yrt{uGmh;~D-LG@sT6VvaFWUd*Jv2Utiy!Y5 z4=)G{-TuR+K!5jxKeL=amAb%_tRGKI_}%k968;#cnd-hT;dA>?Hh%C%+EoU_i_7;$ zAHwnLzd``l^QTRnqf5I!x!B!hMsJDFWB0!CsC~hcALYUGnR@@&@%&WR*rY6y2d@>u zFQ(r!S%SE09oRWGpDzdH1dYP+250+f2mCDt(3P2gk^fBVgqo6zcEN#2F90|F0_Kvv@u5hiE=I34ZW=p9U70$F7$j zSjSZccs-XQyPk8=?Gt$eym)nHU-&^HoW=maiS~FWbns;WT+j8qOW5+F;c1^=7;51O zH}KOrCa1}P;PL#sXUH4QajvfCQU>3q3;xt$;BQ>&j;1sCE9Va6 z@rUV*lWa7AH7*c=1I2B*06CT+j7<{NP`P`0w{5{8~ET+SAj_b8@ho;1WL9a|!aZ-_|Zo z^;*(hGVu33_>yCDC?fJCBj1)wrd-c2JoNbO3;bgS)!SXY{H`tILEphIVSWCxBmVDI z@A@B-m-Tx+mnYzQuIJ*#Urk3|{L*;1!1et3=ek{7&-Gl-Uo>1U55VK^bng9R* diff --git a/src/world/images/atmosphere64.png b/src/world/images/atmosphere64.png deleted file mode 100644 index f586c15e574af3dbc02ee45e4087124d4784af8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2767 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 Vm@HtQQ1$=-002ovPDHLkV1ltHNX-BM diff --git a/src/world/images/original.png b/src/world/images/original.png new file mode 100644 index 0000000000000000000000000000000000000000..4f0a404aef2cf914a8a14503fd0b326f7c70a573 GIT binary patch literal 112776 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
        TQI2ry3u*GGJ_P}r> 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 literal 0 HcmV?d00001 From 152477ebda9658520d78c385f76c49d67b61bfef Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 2 Dec 2016 16:11:20 +0800 Subject: [PATCH 095/109] update vertion to 0.3.4 for Atmosphere effect,#16 --- versions.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/versions.txt b/versions.txt index 80557a2..05b52fe 100644 --- a/versions.txt +++ b/versions.txt @@ -105,4 +105,6 @@ 0.3.2 优化了Camera.update()方法,只有发生用户交互的情况下才实际进行计算。 -0.3.3 优化了Globe.refresh()方法,只有发生用户交互的情况下才重新计算可见切片。 \ No newline at end of file +0.3.3 优化了Globe.refresh()方法,只有发生用户交互的情况下才重新计算可见切片。 + +0.3.4 增加了Atmosphere效果。 \ No newline at end of file From 81f763c837442851229564d26e6e096212e25132 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Fri, 2 Dec 2016 18:25:56 +0800 Subject: [PATCH 096/109] update Poi material,#12 --- src/world/graphics/Poi.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/world/graphics/Poi.ts b/src/world/graphics/Poi.ts index da94e61..a584034 100644 --- a/src/world/graphics/Poi.ts +++ b/src/world/graphics/Poi.ts @@ -20,6 +20,7 @@ void main(void) { `; //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; @@ -27,7 +28,7 @@ uniform sampler2D uSampler; void main() { - gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y)); + gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, gl_PointCoord.y)); } `; From 3148c78b080fde8e6edde6813d40dd6e60867e2a Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 15:07:42 +0800 Subject: [PATCH 097/109] rename TiledLayer.getImageUrl() method to getTileUrl() method --- src/world/Camera.ts | 2 +- src/world/Globe.ts | 4 ++-- src/world/layers/ArcGISTiledLayer.ts | 2 +- src/world/layers/AutonaviTiledLayer.ts | 4 +++- src/world/layers/BingTiledLayer.ts | 4 +++- src/world/layers/BlendTiledLayer.ts | 6 ++++-- src/world/layers/GoogleTiledLayer.ts | 4 +++- src/world/layers/NokiaTiledLayer.ts | 4 +++- src/world/layers/OsmTiledLayer.ts | 4 +++- src/world/layers/SosoTiledLayer.ts | 9 +++++---- src/world/layers/SubTiledLayer.ts | 5 +++-- src/world/layers/TiandituTiledLayer.ts | 4 +++- src/world/layers/TiledLayer.ts | 2 +- 13 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 0908dfb..507bf1b 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -54,7 +54,7 @@ export class CameraCore{ class Camera extends Object3D { private readonly initFov: number; - private readonly animationDuration: number = 600;//层级变化的动画周期是600毫秒 + private readonly animationDuration: number = 200;//层级变化的动画周期,毫秒 private readonly nearFactor: number = 0.6; private readonly baseTheoryDistanceFromCamera2EarthSurface = 1.23 * Kernel.EARTH_RADIUS; private readonly maxPitch = 40; diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 55ee206..269d5db 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -11,7 +11,7 @@ import ImageUtils = require("./Image"); import EventUtils = require("./Event"); class Globe { - REFRESH_INTERVAL: number = 300; //Globe自动刷新时间间隔,以毫秒为单位 + REFRESH_INTERVAL: number = 100; //Globe自动刷新时间间隔,以毫秒为单位 idTimeOut: any = null; //refresh自定刷新的timeOut的handle renderer: Renderer = null; scene: Scene = null; @@ -65,7 +65,7 @@ class Globe { column: n, url: "" }; - args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); + args.url = this.tiledLayer.getTileUrl(args.level, args.row, args.column); var tile = Tile.getInstance(args.level, args.row, args.column, args.url); subLayer1.add(tile); } diff --git a/src/world/layers/ArcGISTiledLayer.ts b/src/world/layers/ArcGISTiledLayer.ts index a83ddcd..c2f1d81 100644 --- a/src/world/layers/ArcGISTiledLayer.ts +++ b/src/world/layers/ArcGISTiledLayer.ts @@ -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/layers/AutonaviTiledLayer.ts b/src/world/layers/AutonaviTiledLayer.ts index 12f2b1e..9822d81 100644 --- a/src/world/layers/AutonaviTiledLayer.ts +++ b/src/world/layers/AutonaviTiledLayer.ts @@ -3,13 +3,15 @@ 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/layers/BingTiledLayer.ts b/src/world/layers/BingTiledLayer.ts index e928a5c..9bc4211 100644 --- a/src/world/layers/BingTiledLayer.ts +++ b/src/world/layers/BingTiledLayer.ts @@ -4,7 +4,8 @@ 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/layers/BlendTiledLayer.ts b/src/world/layers/BlendTiledLayer.ts index 8121243..55bc61e 100644 --- a/src/world/layers/BlendTiledLayer.ts +++ b/src/world/layers/BlendTiledLayer.ts @@ -5,13 +5,15 @@ 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/layers/GoogleTiledLayer.ts b/src/world/layers/GoogleTiledLayer.ts index 0bb800b..da4ced6 100644 --- a/src/world/layers/GoogleTiledLayer.ts +++ b/src/world/layers/GoogleTiledLayer.ts @@ -2,12 +2,14 @@ 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/layers/NokiaTiledLayer.ts b/src/world/layers/NokiaTiledLayer.ts index a594c55..b6e3529 100644 --- a/src/world/layers/NokiaTiledLayer.ts +++ b/src/world/layers/NokiaTiledLayer.ts @@ -2,13 +2,15 @@ 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/layers/OsmTiledLayer.ts b/src/world/layers/OsmTiledLayer.ts index 474dcce..4b46ea1 100644 --- a/src/world/layers/OsmTiledLayer.ts +++ b/src/world/layers/OsmTiledLayer.ts @@ -2,13 +2,15 @@ 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/SosoTiledLayer.ts b/src/world/layers/SosoTiledLayer.ts index 57e8edb..fe84ed7 100644 --- a/src/world/layers/SosoTiledLayer.ts +++ b/src/world/layers/SosoTiledLayer.ts @@ -2,10 +2,11 @@ import TiledLayer = require('./TiledLayer'); class SosoTiledLayer extends TiledLayer { - getImageUrl(level: number, row: number, column: number): string { - // if(level >= 10){ - // return this.getImageUrl2(level, row, column); - // } + + getTileUrl(level: number, row: number, column: number): string { + if(level >= 10){ + return this.getImageUrl2(level, row, column); + } return this.getImageUrl1(level, row, column); } diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index f617053..205c7b2 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -5,10 +5,11 @@ 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 { level: number = -1; - tiledLayer: any = null; + tiledLayer: TiledLayer = null; constructor(args: any) { super(); @@ -95,7 +96,7 @@ class SubTiledLayer extends GraphicGroup { column: tileGridInfo.column, url: "" }; - args.url = this.tiledLayer.getImageUrl(args.level, args.row, args.column); + 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); } diff --git a/src/world/layers/TiandituTiledLayer.ts b/src/world/layers/TiandituTiledLayer.ts index 113e9b1..458d7db 100644 --- a/src/world/layers/TiandituTiledLayer.ts +++ b/src/world/layers/TiandituTiledLayer.ts @@ -2,13 +2,15 @@ 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 index afa39f6..3af01b9 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -29,7 +29,7 @@ abstract class TiledLayer extends GraphicGroup { } //根据切片的层级以及行列号获取图片的url,抽象方法,供子类实现 - abstract getImageUrl(level: number, row: number, column: number): string + abstract getTileUrl(level: number, row: number, column: number): string //根据传入的level更新SubTiledLayer的数量 updateSubLayerCount(level: number) { From 7c5af8f20a4dbc93000157f3221c9f75534130af Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 15:24:57 +0800 Subject: [PATCH 098/109] update SubTiledLayer --- src/world/Globe.ts | 8 ++------ src/world/layers/SubTiledLayer.ts | 4 +--- src/world/layers/TiledLayer.ts | 5 +---- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 269d5db..902cd15 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -46,15 +46,11 @@ class Globe { this.tiledLayer = tiledLayer; this.scene.add(this.tiledLayer, true); //添加第0级的子图层 - var subLayer0 = new SubTiledLayer({ - level: 0 - }); + var subLayer0 = new SubTiledLayer(0); this.tiledLayer.add(subLayer0); //要对level为1的图层进行特殊处理,在创建level为1时就创建其中的全部的四个tile - var subLayer1 = new SubTiledLayer({ - level: 1 - }); + var subLayer1 = new SubTiledLayer(1); this.tiledLayer.add(subLayer1); Kernel.canvas.style.cursor = "wait"; for (var m = 0; m <= 1; m++) { diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index 205c7b2..b27488a 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -8,12 +8,10 @@ import Tile = require('../graphics/Tile'); import TiledLayer = require('./TiledLayer'); class SubTiledLayer extends GraphicGroup { - level: number = -1; tiledLayer: TiledLayer = null; - constructor(args: any) { + constructor(public level: number) { super(); - this.level = args.level; } //重写GraphicGroup的add方法 diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index 3af01b9..260f09c 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -39,10 +39,7 @@ abstract class TiledLayer extends GraphicGroup { if (deltaLevel > 0) { //需要增加子图层 for (i = 0; i < deltaLevel; i++) { - var args = { - level: i + subLayerCount - }; - subLayer = new SubTiledLayer(args); + subLayer = new SubTiledLayer(i + subLayerCount); this.add(subLayer); } } else if (deltaLevel < 0) { From 85a3e449168208b94243e2f655a3105c3e09f117 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 16:10:07 +0800 Subject: [PATCH 099/109] update Globe.ts,#26 --- src/world/Globe.ts | 69 ++++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 902cd15..daa2015 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -2,7 +2,7 @@ import Kernel = require("./Kernel"); import Utils = require("./Utils"); import Renderer = require("./Renderer"); -import Camera, {CameraCore} from "./Camera"; +import Camera, { CameraCore } from "./Camera"; import Scene = require("./Scene"); import TiledLayer = require("./layers/TiledLayer"); import SubTiledLayer = require("./layers/SubTiledLayer"); @@ -11,8 +11,8 @@ import ImageUtils = require("./Image"); import EventUtils = require("./Event"); class Globe { - REFRESH_INTERVAL: number = 100; //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: Camera = null; @@ -30,10 +30,11 @@ class Globe { 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) { @@ -45,6 +46,7 @@ class Globe { } this.tiledLayer = tiledLayer; this.scene.add(this.tiledLayer, true); + //添加第0级的子图层 var subLayer0 = new SubTiledLayer(0); this.tiledLayer.add(subLayer0); @@ -67,66 +69,41 @@ class Globe { } } Kernel.canvas.style.cursor = "default"; - this.tick(); } - getLevel(){ + getLevel() { return this.camera ? this.camera.getLevel() : -1; } setLevel(level: number) { - if(this.camera){ + if (this.camera) { this.camera.setLevel(level); } } - isAnimating(): boolean{ + isAnimating(): boolean { return this.camera.isAnimating(); } - animateToLevel(level: number){ - if(!this.isAnimating()){ + animateToLevel(level: number) { + if (!this.isAnimating()) { level = level > Kernel.MAX_LEVEL ? Kernel.MAX_LEVEL : level; //超过最大的渲染级别就不渲染 - if(level !== this.getLevel()){ + 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) { - try{ - //如果refresh方法出现异常而且没有捕捉,那么就会导致无法继续设置setTimeout,从而无法进一步更新切片 - globe.refresh(); - }catch(e){ - console.error(e); - } - 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(force: boolean = false) { @@ -138,7 +115,7 @@ class Globe { var newCameraCore = this.camera.getCameraCore(); var isNeedRefresh = force || !newCameraCore.equals(this.cameraCore); this.cameraCore = newCameraCore; - if(!isNeedRefresh){ + if (!isNeedRefresh) { return; } var level = this.getLevel() + 3; @@ -159,7 +136,7 @@ class Globe { }); parentTileGrids = Utils.filterRepeatArray(parentTileGrids); } - levelsTileGrids.reverse(); //2-level + levelsTileGrids.reverse(); //2->level for (i = 2; i <= level; i++) { var subLevel = i; var subLayer = this.tiledLayer.children[subLevel]; From 2d6c9280893ee3bc18a264084890b19c2698d56f Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 16:50:40 +0800 Subject: [PATCH 100/109] add refresh() method to TiledLayer,#26 --- src/world/Globe.ts | 24 ++++-------------------- src/world/layers/TiledLayer.ts | 27 +++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index daa2015..d116646 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -118,31 +118,15 @@ class Globe { if (!isNeedRefresh) { return; } - var level = this.getLevel() + 3; - this.tiledLayer.updateSubLayerCount(level); + var lastLevel = this.getLevel() + 3; + this.tiledLayer.updateSubLayerCount(lastLevel); var options = { threshold: 1 }; options.threshold = Math.min(90 / this.camera.getPitch(), 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); - } + var lastLevelTileGrids = this.camera.getVisibleTilesByLevel(lastLevel, options); + this.tiledLayer.refresh(lastLevel, lastLevelTileGrids); } } diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index 260f09c..94eaa47 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -3,16 +3,39 @@ import Kernel = require('../Kernel'); import GraphicGroup = require('../GraphicGroup'); import SubTiledLayer = require('./SubTiledLayer'); import Camera from '../Camera'; +import TileGrid = require('../TileGrid'); +import Utils = require('../Utils'); abstract class TiledLayer extends GraphicGroup { + refresh(lastLevel: number, lastLevelTileGrids: TileGrid[]){ + var levelsTileGrids: any[] = []; //lastLevel->2 + var parentTileGrids = lastLevelTileGrids; + var i: number; + for (i = lastLevel; i >= 2; i--) { + levelsTileGrids.push(parentTileGrids); //此行代码表示第i层级的可见切片 + parentTileGrids = Utils.map(parentTileGrids, function (item) { + return item.getParent(); + }); + parentTileGrids = Utils.filterRepeatArray(parentTileGrids); + } + levelsTileGrids.reverse(); //2->lastLevel + for (i = 2; i <= lastLevel; i++) { + var subLevel = i; + var subLayer = this.children[subLevel]; + subLayer.updateTiles(levelsTileGrids[0], true); + levelsTileGrids.splice(0, 1); + } + } + //重写 draw(camera: Camera){ + var gl = Kernel.gl; //此处将深度测试设置为ALWAYS是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 - Kernel.gl.depthFunc(Kernel.gl.ALWAYS); + gl.depthFunc(gl.ALWAYS); super.draw(camera); //将深度测试恢复成LEQUAL - Kernel.gl.depthFunc(Kernel.gl.LEQUAL); + gl.depthFunc(gl.LEQUAL); } //重写 From ae4af49a8d2e28364aef1e3f81ce756b294a7027 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 18:04:23 +0800 Subject: [PATCH 101/109] update options.threshold in Globe.refresh() method,#26 --- src/world/Camera.ts | 8 +++++++- src/world/Globe.ts | 6 +++--- src/world/layers/TiledLayer.ts | 4 +++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/world/Camera.ts b/src/world/Camera.ts index 507bf1b..07d168b 100644 --- a/src/world/Camera.ts +++ b/src/world/Camera.ts @@ -423,7 +423,13 @@ class Camera extends Object3D { radian = - Math.abs(radian); } - return MathUtils.radianToDegree(radian); + var pitch = MathUtils.radianToDegree(radian); + + if(pitch >= 90){ + throw `Invalid pitch: ${pitch}`; + } + + return pitch; } //计算拾取射线与地球的交点,以笛卡尔空间直角坐标系坐标数组的形式返回 diff --git a/src/world/Globe.ts b/src/world/Globe.ts index d116646..6502174 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -119,14 +119,14 @@ class Globe { return; } var lastLevel = this.getLevel() + 3; - this.tiledLayer.updateSubLayerCount(lastLevel); var options = { threshold: 1 }; - options.threshold = Math.min(90 / this.camera.getPitch(), 1.5); + var pitch = this.camera.getPitch(); + options.threshold = Math.min(90 / (90 - pitch), 1.5); //最大级别的level所对应的可见TileGrids var lastLevelTileGrids = this.camera.getVisibleTilesByLevel(lastLevel, options); - this.tiledLayer.refresh(lastLevel, lastLevelTileGrids); + this.tiledLayer.refresh(lastLevel, lastLevelTileGrids); } } diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index 94eaa47..e933585 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -9,6 +9,8 @@ import Utils = require('../Utils'); abstract class TiledLayer extends GraphicGroup { refresh(lastLevel: number, lastLevelTileGrids: TileGrid[]){ + this._updateSubLayerCount(lastLevel); + var levelsTileGrids: any[] = []; //lastLevel->2 var parentTileGrids = lastLevelTileGrids; var i: number; @@ -55,7 +57,7 @@ abstract class TiledLayer extends GraphicGroup { abstract getTileUrl(level: number, row: number, column: number): string //根据传入的level更新SubTiledLayer的数量 - updateSubLayerCount(level: number) { + private _updateSubLayerCount(level: number) { var subLayerCount = this.children.length; var deltaLevel = level + 1 - subLayerCount; var i: number, subLayer: SubTiledLayer; From 86bf48e7fa4e1d50bba06de132eed4baa8187916 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 18:41:06 +0800 Subject: [PATCH 102/109] init SubLayer 1 in TiledLayer.constructor() method,#26 --- src/world/Globe.ts | 25 ------------------------- src/world/layers/TiledLayer.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 6502174..8b11398 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -5,8 +5,6 @@ import Renderer = require("./Renderer"); import Camera, { CameraCore } from "./Camera"; import Scene = require("./Scene"); import TiledLayer = require("./layers/TiledLayer"); -import SubTiledLayer = require("./layers/SubTiledLayer"); -import Tile = require("./graphics/Tile"); import ImageUtils = require("./Image"); import EventUtils = require("./Event"); @@ -46,29 +44,6 @@ class Globe { } this.tiledLayer = tiledLayer; this.scene.add(this.tiledLayer, true); - - //添加第0级的子图层 - var subLayer0 = new SubTiledLayer(0); - this.tiledLayer.add(subLayer0); - - //要对level为1的图层进行特殊处理,在创建level为1时就创建其中的全部的四个tile - var subLayer1 = new SubTiledLayer(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.getTileUrl(args.level, args.row, args.column); - var tile = Tile.getInstance(args.level, args.row, args.column, args.url); - subLayer1.add(tile); - } - } - Kernel.canvas.style.cursor = "default"; } getLevel() { diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index e933585..4033d1b 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -3,11 +3,38 @@ 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); From 626d8b4a5165e0e5c893861282c7dd0414481ba4 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 18:53:09 +0800 Subject: [PATCH 103/109] update index-src.html and index-bundle.html --- index-bundle.html | 3 +-- index-src.html | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/index-bundle.html b/index-bundle.html index 3691819..c64b6b0 100644 --- a/index-bundle.html +++ b/index-bundle.html @@ -17,10 +17,9 @@ - - + diff --git a/index-src.html b/index-src.html index d6765c4..ef67784 100644 --- a/index-src.html +++ b/index-src.html @@ -25,10 +25,9 @@ - - + From ec1877ebf93f91c96f2ccf8eb1c153364f6edb92 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 19:19:30 +0800 Subject: [PATCH 104/109] optimize Renderer,#26 --- src/world/Globe.ts | 2 +- src/world/Kernel.ts | 1 - src/world/Renderer.ts | 29 ++++++++++++----------------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index 8b11398..d229e0c 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -19,7 +19,7 @@ class Globe { constructor(canvas: HTMLCanvasElement) { Kernel.globe = this; - this.renderer = Kernel.renderer = new Renderer(canvas); + this.renderer = new Renderer(canvas); this.scene = new Scene(); var radio = canvas.width / canvas.height; this.camera = new Camera(30, radio, 1, Kernel.EARTH_RADIUS * 3); diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts index 69eb736..5f92154 100644 --- a/src/world/Kernel.ts +++ b/src/world/Kernel.ts @@ -9,7 +9,6 @@ const maxProjectedCoord = Math.PI * radius; const Kernel = { gl: null, canvas: null, - renderer: null, globe: null, idCounter: 0, //Object3D对象的唯一标识 BASE_LEVEL: 6, //渲染的基准层级 diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts index c63e1b7..8b9e67b 100644 --- a/src/world/Renderer.ts +++ b/src/world/Renderer.ts @@ -3,17 +3,14 @@ import Kernel = require("./Kernel"); import EventUtils = require("./Event"); import Scene = require("./Scene"); import Camera from "./Camera"; -import {WebGLRenderingContextExtension, WebGLProgramExtension} from "./Definitions"; +import { WebGLRenderingContextExtension, WebGLProgramExtension } from "./Definitions"; class Renderer { scene: Scene = null; camera: Camera = null; - bAutoRefresh: boolean = false; + autoRefresh: boolean = false; constructor(canvas: HTMLCanvasElement) { - //之所以在此处设置Kernel.renderer是因为要在tick函数中使用 - Kernel.renderer = this; - EventUtils.bindEvents(canvas); var gl: WebGLRenderingContextExtension; @@ -77,22 +74,20 @@ class Renderer { 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(); } } } From 016ede8fe7e5a04978269418b889dc8b90fd3e83 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 20:54:41 +0800 Subject: [PATCH 105/109] update TiledLayer.refresh() method,#26 --- src/world/graphics/Tile.ts | 3 ++- src/world/layers/SubTiledLayer.ts | 3 +-- src/world/layers/TiledLayer.ts | 18 ++++++++---------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/world/graphics/Tile.ts b/src/world/graphics/Tile.ts index 08efa88..87933c8 100644 --- a/src/world/graphics/Tile.ts +++ b/src/world/graphics/Tile.ts @@ -7,6 +7,7 @@ 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已经设置为一般形式 @@ -149,7 +150,7 @@ class TileInfo { } class Tile extends MeshGraphic { - subTiledLayer: any; + subTiledLayer: SubTiledLayer; private constructor(public geometry: TileGeometry, public material: TileMaterial, public tileInfo: TileInfo) { super(geometry, material); diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index b27488a..62f908e 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -41,8 +41,7 @@ class SubTiledLayer extends GraphicGroup { } //根据传入的tiles信息进行更新其children - updateTiles(visibleTileGrids: TileGrid[], bAddNew: boolean) { //camera,options - //var visibleTileGrids = camera.getVisibleTilesByLevel(this.level,options); + updateTiles(visibleTileGrids: TileGrid[], bAddNew: boolean) { //检查visibleTileGrids中是否存在指定的切片信息 function checkTileExist(tileArray: TileGrid[], lev: number, row: number, col: number): any { var result = { diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index 4033d1b..e6e787b 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -38,22 +38,20 @@ abstract class TiledLayer extends GraphicGroup { refresh(lastLevel: number, lastLevelTileGrids: TileGrid[]){ this._updateSubLayerCount(lastLevel); - var levelsTileGrids: any[] = []; //lastLevel->2 + var levelsTileGrids: TileGrid[][] = []; var parentTileGrids = lastLevelTileGrids; - var i: number; - for (i = lastLevel; i >= 2; i--) { - levelsTileGrids.push(parentTileGrids); //此行代码表示第i层级的可见切片 + 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); } - levelsTileGrids.reverse(); //2->lastLevel - for (i = 2; i <= lastLevel; i++) { - var subLevel = i; - var subLayer = this.children[subLevel]; - subLayer.updateTiles(levelsTileGrids[0], true); - levelsTileGrids.splice(0, 1); + + for (subLevel = 2; subLevel <= lastLevel; subLevel++) { + (this.children[subLevel]).updateTiles(levelsTileGrids[subLevel], true); } } From e2ad1623032cac9eeb92bb8c8ee51b1c00e9bff3 Mon Sep 17 00:00:00 2001 From: iSpring Date: Sat, 3 Dec 2016 23:18:27 +0800 Subject: [PATCH 106/109] update,#26 --- src/world/layers/SubTiledLayer.ts | 1 + src/world/layers/TiledLayer.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/world/layers/SubTiledLayer.ts b/src/world/layers/SubTiledLayer.ts index 62f908e..7a5f069 100644 --- a/src/world/layers/SubTiledLayer.ts +++ b/src/world/layers/SubTiledLayer.ts @@ -85,6 +85,7 @@ class SubTiledLayer extends GraphicGroup { 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 = { diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index e6e787b..0640d64 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -50,6 +50,8 @@ abstract class TiledLayer extends GraphicGroup { parentTileGrids = Utils.filterRepeatArray(parentTileGrids); } + console.log("----------------------------------------------------------"); + for (subLevel = 2; subLevel <= lastLevel; subLevel++) { (this.children[subLevel]).updateTiles(levelsTileGrids[subLevel], true); } From 059267cdc802d10448958791dc693c5006146b8b Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Mon, 5 Dec 2016 13:11:31 +0800 Subject: [PATCH 107/109] cancel the image request when destory MeshTextureMaterial,#26 --- src/world/materials/MeshTextureMaterial.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/world/materials/MeshTextureMaterial.ts index 8ce04d2..33d53c8 100644 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/world/materials/MeshTextureMaterial.ts @@ -9,7 +9,6 @@ type ImageType = HTMLImageElement | string; class MeshTextureMaterial extends Material { texture: WebGLTexture; image: HTMLImageElement; - url: string; private ready:boolean = false; private deleted: boolean = false; @@ -26,7 +25,7 @@ class MeshTextureMaterial extends Material { } isReady(): boolean{ - return this.ready; + return this.ready && !this.deleted; } setImageOrUrl(imageOrUrl?: ImageType){ @@ -63,7 +62,7 @@ class MeshTextureMaterial extends Material { } //图片加载完成时触发 - onLoad() { + protected onLoad() { //要考虑纹理已经被移除掉了图片才进入onLoad这种情况 if (this.deleted) { return; @@ -105,9 +104,17 @@ class MeshTextureMaterial extends Material { //释放显卡中的texture资源 destroy() { var gl = Kernel.gl; - if (gl.isTexture(this.texture)) { + // 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; } From 1288e944eb6cefbf1a896cd401b69aeb20709db0 Mon Sep 17 00:00:00 2001 From: iSpring Date: Tue, 6 Dec 2016 00:04:56 +0800 Subject: [PATCH 108/109] update SosoTiledLayer,#26 --- src/world/Globe.ts | 1 + src/world/layers/SosoTiledLayer.ts | 32 ++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/world/Globe.ts b/src/world/Globe.ts index d229e0c..f71e23a 100644 --- a/src/world/Globe.ts +++ b/src/world/Globe.ts @@ -44,6 +44,7 @@ class Globe { } this.tiledLayer = tiledLayer; this.scene.add(this.tiledLayer, true); + this.refresh(true); } getLevel() { diff --git a/src/world/layers/SosoTiledLayer.ts b/src/world/layers/SosoTiledLayer.ts index fe84ed7..4c508d3 100644 --- a/src/world/layers/SosoTiledLayer.ts +++ b/src/world/layers/SosoTiledLayer.ts @@ -5,15 +5,16 @@ class SosoTiledLayer extends TiledLayer { getTileUrl(level: number, row: number, column: number): string { if(level >= 10){ - return this.getImageUrl2(level, row, column); + return this._getPoliticalUrl(level, row, column); } - return this.getImageUrl1(level, row, column); + //return this._getDemUrl(level, row, column); + return this._getImageUrl(level, row, column); } - getImageUrl1(level: number, row: number, column: number): string { - //影像 - var url = ""; + //地形图 + 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; @@ -21,13 +22,28 @@ class SosoTiledLayer extends TiledLayer { 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 sateUrl = "//p" + serverIdx + ".map.soso.com/sateTiles/" + level + "/" + A + "/" + B + "/" + a + "_" + b + ".jpg"; - url = sateUrl; + 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; } - getImageUrl2(level: number, row: number, column: number): string { + //行政区划图 + 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; From 32ada135cf766aef1e3b83b24bbdda81a693a571 Mon Sep 17 00:00:00 2001 From: Qun Sun Date: Wed, 7 Dec 2016 12:56:31 +0800 Subject: [PATCH 109/109] optimize TiledLayer.refresh() method by calculating addNew parameter used for SubTiledLayer.updateTiles() method,#26 --- src/world/layers/TiledLayer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/world/layers/TiledLayer.ts b/src/world/layers/TiledLayer.ts index 0640d64..ac3b44e 100644 --- a/src/world/layers/TiledLayer.ts +++ b/src/world/layers/TiledLayer.ts @@ -13,8 +13,8 @@ abstract class TiledLayer extends GraphicGroup { super(); //添加第0级的子图层 - // var subLayer0 = new SubTiledLayer(0); - // this.add(subLayer0); + var subLayer0 = new SubTiledLayer(0); + this.add(subLayer0); //要对level为1的图层进行特殊处理,在创建level为1时就创建其中的全部的四个tile var subLayer1 = new SubTiledLayer(1); @@ -53,7 +53,8 @@ abstract class TiledLayer extends GraphicGroup { console.log("----------------------------------------------------------"); for (subLevel = 2; subLevel <= lastLevel; subLevel++) { - (this.children[subLevel]).updateTiles(levelsTileGrids[subLevel], true); + var addNew = lastLevel === subLevel || (lastLevel - subLevel) > 2; + (this.children[subLevel]).updateTiles(levelsTileGrids[subLevel], addNew); } }