├── LICENSE.txt ├── README.md ├── example ├── client.html └── frames.html ├── src ├── WCS.js ├── WMTS.js ├── topo2.layers.js ├── universe.js └── wxs.three.js ├── three ├── TrackballControls.js ├── three.js └── three.min.js ├── tiff-js └── tiff.js └── wxs.three.html /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2010-2014 wxs.threejs authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wxs.threejs 2 | =========== 3 | 4 | *** WARNING *** 5 | Major reworking of structure. 6 | Some old functionality is temporarily gone. 7 | 8 | Realtime consumption of wxs-services 9 | 10 | 11 | RawGit: 12 | 13 | http://rawgit.com/jarped/wxs.threejs/master/example/frames.html 14 | 15 | 16 | Additional information: 17 | 18 | http://labs.kartverket.no/wcs-i-threejs/ 19 | 20 | 21 | Example-calls to the WCS: 22 | 23 | http://labs.kartverket.no/wcs-i-quantum-gis/ 24 | 25 | 26 | The following licenses apply: 27 | 28 | threejs: https://github.com/mrdoob/three.js/blob/master/LICENSE 29 | 30 | tiff-js: https://github.com/GPHemsley/tiff-js/blob/master/LICENSE 31 | 32 | openLayers: https://github.com/openlayers/openlayers/blob/master/license.txt 33 | 34 | 35 | The solution uses web services from Kartverket which are subject to their own licenses (mostly CC-BY 3.0 Norway) and the Norwegian Geodata law. See http://kartverket.no/Kart/Kartverksted/Lisens/ for the license terms and http://kartverket.no/Kart/Kartverksted/ for details on the web services. 36 | -------------------------------------------------------------------------------- /example/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WMST 5 | 25 | 26 | 130 | 131 | 132 |
133 | 134 | 135 | -------------------------------------------------------------------------------- /example/frames.html: -------------------------------------------------------------------------------- 1 | 2 | 43 | 44 |

WXS.THREE Openlayers example

45 | 46 | 49 | 52 | 55 |
47 | 48 | 50 | 51 | 53 | 54 |
56 | 57 |
58 | 59 |
60 | 61 | 62 | 63 |
64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/WCS.js: -------------------------------------------------------------------------------- 1 | var wxs3 = wxs3 || {}; 2 | 3 | (function (ns) { 4 | 'use strict'; 5 | 6 | ns.WCS = function (tileSpanX, tileSpanY, wcsWidth, wcsHeight) { 7 | this.tileSpanX = tileSpanX; 8 | this.tileSpanY = tileSpanY; 9 | this.wcsWidth = wcsWidth; 10 | this.wcsHeight = wcsHeight; 11 | this.geometry = new THREE.PlaneGeometry(tileSpanX, tileSpanY, wcsWidth, wcsHeight); 12 | this.tries=0; 13 | this.maxTries=5; 14 | this.time=Date.now(); 15 | }; 16 | 17 | 18 | ns.WCS.prototype.wcsFetcher = function (WMTSCall) { 19 | this.WMTSCall=WMTSCall; 20 | var demTileRequest = new XMLHttpRequest(); 21 | var that = this; 22 | demTileRequest.open('GET', WMTSCall.url.wcs, true); 23 | demTileRequest.responseType = 'arraybuffer'; 24 | demTileRequest.onreadystatechange = function () { 25 | var tiffArray,tiffParser; 26 | if (this.readyState === 4) { 27 | try{ 28 | tiffParser = new TIFFParser(); 29 | tiffArray = tiffParser.parseTIFF(this.response); 30 | that.updateGeometry(tiffArray[0], that.geometry); 31 | } 32 | catch(e){ 33 | //console.log('ERROR: WCS-call for ' + WMTSCall.zoom + '_' + WMTSCall.tileRow + '_' + WMTSCall.tileCol + ' failed.'); 34 | if (that.tries= length - this.dim.demWidth) { 47 | this.edges.bottom.push(i); 48 | if (i == length - this.dim.demWidth) { 49 | this.edges.left.push(i); 50 | } 51 | else if (i == length - 1) { 52 | this.edges.right.push(i); 53 | } 54 | } 55 | else if (i % this.dim.demWidth == 0) this.edges.left.push(i); 56 | else if ((i + 1) % this.dim.demWidth == 0) this.edges.right.push(i); 57 | } 58 | 59 | this.edges.topLeft=this.edges.top[0]; 60 | this.edges.topRight=this.edges.right[0]; 61 | this.edges.bottomLeft=this.edges.bottom[0]; 62 | this.edges.bottomRight=this.edges.bottom[this.dim.demWidth-1]; 63 | 64 | 65 | 66 | this.dim.wmsLayers = layers; 67 | this.createRenderer(); 68 | this.createScene(); 69 | this.createCamera(); 70 | this.createControls(); 71 | this.foregroundGroup = new THREE.Object3D(); 72 | this.backgroundGroup = new THREE.Object3D(); 73 | this.foregroundGroup.scale.z = dim.zMult; 74 | this.scene.add(this.foregroundGroup); 75 | this.scene.add(this.backgroundGroup); 76 | 77 | // Generate tiles and boundingboxes 78 | this.generateTiles(); 79 | document.getElementById('webgl').appendChild(this.renderer.domElement); 80 | this.render(); 81 | }; 82 | 83 | ns.ThreeDMap.prototype.createRenderer = function () { 84 | this.renderer = new THREE.WebGLRenderer( 85 | { 86 | //antialias: true 87 | } 88 | 89 | ); 90 | this.renderer.setSize(this.dim.width, this.dim.height); 91 | }; 92 | 93 | ns.ThreeDMap.prototype.createScene = function () { 94 | this.scene = new THREE.Scene(); 95 | this.scene.add(new THREE.AmbientLight(0xeeeeee)); 96 | }; 97 | 98 | ns.ThreeDMap.prototype.createCamera = function () { 99 | var centerY, centerX, cameraHeight; 100 | var fov = 45; 101 | this.camera = new THREE.PerspectiveCamera( 102 | fov, 103 | this.dim.width / this.dim.height, 104 | 0.1, 105 | 5000000 106 | ); 107 | // Some trig to find height for camera 108 | if (!!this.dim.Z) { 109 | cameraHeight = this.dim.Z; 110 | } else { 111 | cameraHeight = (this.dim.metersHeight / 2) / Math.tan((fov / 2) * Math.PI / 180); 112 | } 113 | // Place camera in middle of bbox 114 | centerX = (this.dim.minx + this.dim.maxx) / 2; 115 | centerY = (this.dim.miny + this.dim.maxy) / 2; 116 | this.camera.position.set(centerX, centerY, cameraHeight); 117 | this.raycaster = new THREE.Raycaster(this.camera.position, this.vector); 118 | }; 119 | 120 | ns.ThreeDMap.prototype.createControls = function () { 121 | var centerY; 122 | var centerX; 123 | this.controls = new THREE.TrackballControls(this.camera); 124 | // Point camera directly down 125 | centerX = (this.dim.minx + this.dim.maxx) / 2; 126 | centerY = (this.dim.miny + this.dim.maxy) / 2; 127 | this.controls.target = new THREE.Vector3(centerX, centerY, 0); 128 | }; 129 | 130 | ns.ThreeDMap.prototype.render = function () { 131 | var i; 132 | for (i = 0; i < this.foregroundGroup.children.length; i++) { 133 | if (this.foregroundGroup.children[i].scale.z < 1 && this.foregroundGroup.children[i].geometry.loaded == true) { 134 | this.foregroundGroup.children[i].scale.z += 0.02; 135 | 136 | } 137 | else if (this.foregroundGroup.children[i].scale.z >= 1) { 138 | //this.foregroundGroup.children[i].material.wireframe=false; 139 | if (this.foregroundGroup.children[i].geometry.processed['all'] == false) { 140 | this.neighbourTest(this.foregroundGroup.children[i].WMTSCall); 141 | } 142 | } 143 | } 144 | this.controls.update(); 145 | window.requestAnimationFrame(this.render.bind(this)); 146 | this.renderer.render(this.scene, this.camera); 147 | this.caster(); 148 | }; 149 | 150 | ns.ThreeDMap.prototype.generateTiles = function () { 151 | var capabilitiesURL = this.dim.wmtsUrl+'?Version=1.0.0&service=WMTS&request=getcapabilities'; 152 | var WMTSCapabilities = new ns.WMTS(capabilitiesURL, this.dim.crs, this.dim.wmtsLayer); 153 | var that = this; 154 | WMTSCapabilities.fetchCapabilities(function (tileMatrixSet) { 155 | that.bbox2tiles(tileMatrixSet); 156 | }) 157 | }; 158 | 159 | ns.ThreeDMap.prototype.bbox2tiles = function (tileMatrixSet) { 160 | var i, tmpBounds, tileMatrix, spanDivisor, tileMatrixCount; 161 | var bounds = this.dim.getBounds(); 162 | var WMTSCalls = []; 163 | var querySpanX = bounds.maxx - bounds.minx; 164 | var querySpanY = bounds.maxy - bounds.miny; 165 | var querySpanMin, querySpanMax, querySpanMinDim, querySpanMaxDim; 166 | 167 | if (querySpanX > querySpanY) { 168 | querySpanMin = querySpanY; 169 | querySpanMax = querySpanX; 170 | querySpanMinDim = 'y'; 171 | querySpanMaxDim = 'x'; 172 | } 173 | else { 174 | querySpanMin = querySpanX; 175 | querySpanMax = querySpanY; 176 | querySpanMinDim = 'x'; 177 | querySpanMaxDim = 'y'; 178 | } 179 | tileMatrixCount = tileMatrixSet.length; 180 | 181 | // Here we find the first matrix that has a tilespan smaller than that of the smallest dimension of the input bbox. 182 | // We can control the resolution of the images by altering how large a difference there must be (half, quarter etc.) 183 | spanDivisor = 4; 184 | for (tileMatrix = 0; tileMatrix < tileMatrixCount; tileMatrix++) { 185 | if (querySpanMinDim == 'x') { 186 | if (tileMatrixSet[tileMatrix].TileSpanX < querySpanMin / spanDivisor) { 187 | this.foregroundMatrix = tileMatrixSet[tileMatrix]; 188 | this.backgroundMatrix = tileMatrixSet[tileMatrix - 1]; 189 | break; 190 | } 191 | } 192 | else if (tileMatrixSet[tileMatrix].TileSpanY < querySpanMin / spanDivisor) { 193 | this.foregroundMatrix = tileMatrixSet[tileMatrix]; 194 | this.backgroundMatrix = tileMatrixSet[tileMatrix - 1]; 195 | break; 196 | } 197 | } 198 | tmpBounds = new THREE.Vector2((bounds.maxx + bounds.minx) / 2, (bounds.maxy + bounds.miny) / 2); 199 | WMTSCalls = this.centralTileFetcher(tmpBounds, this.backgroundMatrix); 200 | this.tileLoader(WMTSCalls, false); 201 | for (i = 0; i < WMTSCalls.length; i++) { 202 | this.mainTileLoader({zoom: WMTSCalls[i].zoom, tileRow: WMTSCalls[i].tileRow, tileCol: WMTSCalls[i].tileCol}); 203 | 204 | } 205 | 206 | }; 207 | 208 | ns.ThreeDMap.prototype.centralTileFetcher = function (bounds, activeMatrix) { 209 | var tr, tc; 210 | var WMTSCalls = []; 211 | var name = null; 212 | var tileCol = Math.floor((bounds.x - activeMatrix.TopLeftCorner.minx) / activeMatrix.TileSpanX); 213 | var tileRow = Math.floor((activeMatrix.TopLeftCorner.maxy - bounds.y) / activeMatrix.TileSpanY); 214 | var tileColMin = tileCol - 1; 215 | var tileRowMin = tileRow - 1; 216 | var tileColMax = tileCol + 1; 217 | var tileRowMax = tileRow + 1; 218 | // Here we generate tileColumns and tileRows as well as translate tilecol and tilerow to boundingboxes 219 | for (tc = tileColMin; tc <= tileColMax; tc++) 220 | for (tr = tileRowMin; tr <= tileRowMax; tr++) { 221 | name = activeMatrix.Zoom + '_' + tr + '_' + tc; 222 | if (this.backgroundTiles.indexOf(name) == -1) { 223 | this.backgroundTiles.push(name); 224 | WMTSCalls.push(this.singleTileFetcher(tc, tr, activeMatrix)); 225 | } 226 | } 227 | return WMTSCalls; 228 | }; 229 | 230 | ns.ThreeDMap.prototype.singleTileFetcher = function (tileCol, tileRow, activeMatrix) { 231 | var WMTSCall; 232 | var wmsBounds = [ 233 | activeMatrix.TopLeftCorner.minx + (tileCol * activeMatrix.TileSpanX), 234 | activeMatrix.TopLeftCorner.maxy - ((tileRow + 1) * activeMatrix.TileSpanY), 235 | activeMatrix.TopLeftCorner.minx + ((tileCol + 1) * activeMatrix.TileSpanX), 236 | activeMatrix.TopLeftCorner.maxy - ((tileRow) * activeMatrix.TileSpanY) 237 | ]; 238 | var TileSpanY = activeMatrix.TileSpanY; 239 | var TileSpanX = activeMatrix.TileSpanX; 240 | var wcsDivisor = 2; 241 | var grid2rasterUnitsX = ((TileSpanX / (this.dim.demHeight - 1))); 242 | var grid2rasterUnitsY = ((TileSpanY / (this.dim.demWidth - 1))); 243 | var wcsBounds = [ 244 | // Add some to the extents as we need to put values from a raster onto a grid. Bazingah! 245 | (wmsBounds[0] - (grid2rasterUnitsX / wcsDivisor)), //minx 246 | (wmsBounds[1] - (grid2rasterUnitsY / wcsDivisor)), //miny 247 | (wmsBounds[2] + (grid2rasterUnitsX / wcsDivisor)), //maxx 248 | (wmsBounds[3] + (grid2rasterUnitsY / wcsDivisor)) //maxy 249 | ]; 250 | WMTSCall = { 251 | tileSpanX: TileSpanX, 252 | tileSpanY: TileSpanY, 253 | tileRow: tileRow, 254 | tileCol: tileCol, 255 | zoom: activeMatrix.Zoom, 256 | // Setting these for easy debugging 257 | // TODO: define parameters here for reuse later on 258 | url: { 259 | cache_WMTS: this.dim.wmtsUrl+'?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&Style=default&Format=image/png&Layer='+ this.dim.wmtsLayer+'&TileMatrixSet=' + activeMatrix.TileMatrixSetIdentifier + '&TileMatrix=' + activeMatrix.Identifier + '&TileRow=' + tileRow + '&TileCol=' + tileCol, 260 | cache_wms: this.dim.wmscUrl +'REQUEST=GetMap&SERVICE=WMS&VERSION=1.3.0&Layer=topo2&Style=default&Format=image/png&width=256&height=256&crs=EPSG:' + activeMatrix.Identifier.split(':')[1] + '&BBOX=' + wmsBounds.join(','), 261 | wms: this.dim.wmsUrl + '?GKT='+this.dim.gatekeeperTicket+'&REQUEST=GetMap&SERVICE=WMS&VERSION=1.1.1&Layers='+this.dim.wmsLayers+'&Style=default&Format=image/jpeg&WIDTH=256&HEIGHT=256&SRS=EPSG:' + activeMatrix.Identifier.split(':')[1] + '&BBOX=' + wmsBounds.join(','), 262 | //wcs 1.1.0 NOT WORKING with XYZ - needs to drop xml-part to use tiff-js? 263 | //wcs: 'http://wcs.geonorge.no/skwms1/wcs.dtm?SERVICE=WCS&VERSION=1.1.0&REQUEST=GetCoverage&FORMAT=geotiff&IDENTIFIER=all_50m&BOUNDINGBOX='+ wcsBounds.join(',') +',urn:ogc:def:crs:EPSG::'+activeMatrix.Identifier.split(':')[1] + '&GridBaseCRS=urn:ogc:def:crs:EPSG::'+activeMatrix.Identifier.split(':')[1] + '&GridCS=urn:ogc:def:crs:EPSG::'+activeMatrix.Identifier.split(':')[1] + '&GridType=urn:ogc:def:method:WCS:1.1:2dGridIn2dCrs&GridOrigin=' +wmsBounds[0] +',' +wmsBounds[1] +'&GridOffsets='+grid2rasterUnitsX +',' +grid2rasterUnitsY + '&RangeSubset=50m:average' //[bands[1]]' 264 | wcs: this.dim.wcsUrl+'?SERVICE=WCS&VERSION=1.0.0&REQUEST=GetCoverage&FORMAT=geotiff&WIDTH=' + parseInt(dim.demWidth) + '&HEIGHT=' + parseInt(dim.demWidth) + '&COVERAGE=' + dim.coverage + '&crs=EPSG:' + this.dim.crs + '&BBOX=' + wcsBounds.join(',') // + '&INTERPOLATION=BILINEAR' //+'&RESPONSE_CRS=EPSG:'+activeMatrix.Identifier.split(':')[1] //+ '&RangeSubset=50m:average[bands[1]]' +'&RESX='+grid2rasterUnitsX+'&RESY='+grid2rasterUnitsY 265 | }, 266 | bounds: { 267 | minx: wmsBounds[0], 268 | miny: wmsBounds[1], 269 | maxx: wmsBounds[2], 270 | maxy: wmsBounds[3] 271 | } 272 | }; 273 | return WMTSCall; 274 | }; 275 | 276 | ns.ThreeDMap.prototype.caster = function () { 277 | var tileName = null; 278 | this.vector = new THREE.Vector3(0, 0, -1); 279 | this.vector.applyQuaternion(this.camera.quaternion); 280 | this.raycaster = new THREE.Raycaster(this.camera.position, this.vector); 281 | this.intersects = this.raycaster.intersectObjects(this.backgroundGroup.children); 282 | if (this.intersects.length > 0) { 283 | tileName = this.intersects[0].object.tileName; 284 | this.mainTileLoader(tileName); 285 | 286 | } 287 | }; 288 | ns.ThreeDMap.prototype.mainTileLoader = function (tileName) { 289 | var neighbourCall; 290 | var neighbourCalls = this.backGroundTileNeighbours(tileName); 291 | // add foreground 292 | var children = this.tileChildren(tileName); 293 | this.tileLoader(children, true); 294 | // add backgound 295 | for (neighbourCall = 0; neighbourCall < neighbourCalls.length; neighbourCall++) { 296 | this.tileLoader([ neighbourCalls[neighbourCall] ], false); 297 | } 298 | // remove processed background 299 | // TODO: Find out if we need to run geometry.dispose() first. 300 | this.backgroundGroup.getObjectByName(tileName.zoom + '_' + tileName.tileRow + '_' + tileName.tileCol).geometry.dispose(); 301 | this.backgroundGroup.remove(this.backgroundGroup.getObjectByName(tileName.zoom + '_' + tileName.tileRow + '_' + tileName.tileCol)); 302 | }; 303 | 304 | ns.ThreeDMap.prototype.tileChildren = function (tileName) { 305 | var tr, tc; 306 | var WMTSCalls = []; 307 | var tileCol = tileName.tileCol * 2; 308 | var tileRow = tileName.tileRow * 2; 309 | var tileColMin = tileCol; 310 | var tileRowMin = tileRow; 311 | var tileColMax = tileCol + 1; 312 | var tileRowMax = tileRow + 1; 313 | // Here we generate tileColumns and tileRows as well as translate tilecol and tilerow to boundingboxes 314 | for (tc = tileColMin; tc <= tileColMax; tc++) 315 | for (tr = tileRowMin; tr <= tileRowMax; tr++) 316 | //if (this.foregroundTiles.indexOf(name.zoom+'_'+tr+'_'+tc) ==-1) { 317 | if (typeof this.foregroundGroup.getObjectByName(tileName.zoom + '_' + tr + '_' + tc) === "undefined") { 318 | // Add tile to index over loaded tiles 319 | // TODO: Do we still use this? 320 | this.foregroundTiles.push((tileName.zoom + 1) + '_' + tr + '_' + tc); 321 | WMTSCalls.push(this.singleTileFetcher(tc, tr, this.foregroundMatrix)); 322 | } 323 | return WMTSCalls; 324 | }; 325 | 326 | ns.ThreeDMap.prototype.backGroundTileNeighbours = function (tileName) { 327 | var tr, tc; 328 | var WMTSCalls = []; 329 | var tileCol = tileName.tileCol; 330 | var tileRow = tileName.tileRow; 331 | var tileColMin = tileCol - 1; 332 | var tileRowMin = tileRow - 1; 333 | var tileColMax = tileCol + 1; 334 | var tileRowMax = tileRow + 1; 335 | // Here we generate tileColumns and tileRows as well as translate tilecol and tilerow to boundingboxes 336 | for (tc = tileColMin; tc <= tileColMax; tc++) 337 | for (tr = tileRowMin; tr <= tileRowMax; tr++){ 338 | // TODO: Why do we still use this instead of backgroundGroup.getObjectByName() 339 | if (this.backgroundTiles.indexOf(tileName.zoom + '_' + tr + '_' + tc) == -1) { 340 | this.backgroundTiles.push(tileName.zoom + '_' + tr + '_' + tc); 341 | WMTSCalls.push(this.singleTileFetcher(tc, tr, this.backgroundMatrix)); 342 | } 343 | } 344 | return WMTSCalls; 345 | }; 346 | 347 | ns.ThreeDMap.prototype.tileLoader = function (WMTSCalls, visible) { 348 | var WCSTile, concatName, geometry, material, i; 349 | for (i = 0; i < WMTSCalls.length; i++) { 350 | material = null; 351 | geometry = null; 352 | concatName = WMTSCalls[i].zoom + '_' + WMTSCalls[i].tileRow + '_' + WMTSCalls[i].tileCol; 353 | if (visible) { 354 | // Hack for CORS? 355 | THREE.ImageUtils.crossOrigin = ""; 356 | 357 | WCSTile = new ns.WCS(WMTSCalls[i].tileSpanX, WMTSCalls[i].tileSpanY, dim.demWidth - 1, dim.demHeight - 1); 358 | WCSTile.wcsFetcher(WMTSCalls[i]); 359 | geometry = WCSTile.geometry; 360 | geometry.processed = { 361 | left: false, 362 | right: false, 363 | top: false, 364 | bottom: false, 365 | topLeft: false, 366 | topRight: false, 367 | bottomLeft: false, 368 | bottomRight: false, 369 | allSides: false, 370 | all: false 371 | }; 372 | 373 | var activeUrl; 374 | if (this.dim.wmsUrl) 375 | activeUrl=WMTSCalls[i].url.wms; 376 | else 377 | activeUrl=WMTSCalls[i].url.cache_WMTS; 378 | 379 | // TODO: Create a loader for images based on the WCS-loader. This allows us to check if the image actually loads and reload if something fails. Also, we can use wireframes as a placeholder. 380 | material = new THREE.MeshBasicMaterial( 381 | { 382 | 383 | map: THREE.ImageUtils.loadTexture( 384 | activeUrl, 385 | new THREE.UVMapping() 386 | ), 387 | 388 | //side: THREE.DoubleSide 389 | //wireframe: true 390 | } 391 | ); 392 | //material.depthWrite=false; 393 | //material.map.image.hidden=true; 394 | } 395 | else { 396 | geometry = new THREE.PlaneGeometry(WMTSCalls[i].tileSpanX, WMTSCalls[i].tileSpanY); 397 | material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }); 398 | } 399 | this.mesh = new THREE.Mesh( 400 | geometry, 401 | material 402 | ); 403 | this.mesh.position.x = WMTSCalls[i].bounds.minx + (WMTSCalls[i].tileSpanX / 2); 404 | this.mesh.position.y = WMTSCalls[i].bounds.miny + (WMTSCalls[i].tileSpanY / 2); 405 | this.mesh.tileName = { 406 | zoom: WMTSCalls[i].zoom, 407 | tileRow: WMTSCalls[i].tileRow, 408 | tileCol: WMTSCalls[i].tileCol 409 | }; 410 | this.mesh.name = concatName; 411 | this.mesh.bounds = WMTSCalls[i].bounds; 412 | this.mesh.url = WMTSCalls[i].url; 413 | this.mesh.scale.z = 0.02; 414 | this.mesh.WMTSCall = WMTSCalls[i]; 415 | this.tileLoaded(this.mesh, visible); 416 | } 417 | }; 418 | 419 | ns.ThreeDMap.prototype.tileLoaded = function (tile, visible) { 420 | tile.visible = visible; 421 | if (visible) { 422 | this.foregroundGroup.add(tile); 423 | } 424 | else { 425 | this.backgroundGroup.add(tile); 426 | } 427 | }; 428 | 429 | ns.ThreeDMap.prototype.neighbourTest = function (WMTSCall) { 430 | var name = WMTSCall.zoom + '_' + (WMTSCall.tileRow) + '_' + WMTSCall.tileCol; 431 | var neighbours={ 432 | top: WMTSCall.zoom + '_' + (WMTSCall.tileRow - 1) + '_' + WMTSCall.tileCol, 433 | bottom: WMTSCall.zoom + '_' + (WMTSCall.tileRow + 1) + '_' + WMTSCall.tileCol, 434 | left: WMTSCall.zoom + '_' + WMTSCall.tileRow + '_' + (WMTSCall.tileCol - 1), 435 | right: WMTSCall.zoom + '_' + WMTSCall.tileRow + '_' + (WMTSCall.tileCol + 1), 436 | topLeft: WMTSCall.zoom+'_'+ (WMTSCall.tileRow -1) +'_'+(WMTSCall.tileCol -1), 437 | topRight: WMTSCall.zoom+'_'+ (WMTSCall.tileRow -1) +'_'+ (WMTSCall.tileCol +1), 438 | bottomLeft: WMTSCall.zoom+'_'+ (WMTSCall.tileRow +1) +'_'+(WMTSCall.tileCol -1), 439 | bottomRight: WMTSCall.zoom+'_'+ (WMTSCall.tileRow +1) +'_'+(WMTSCall.tileCol +1) 440 | } 441 | 442 | var tile = this.foregroundGroup.getObjectByName(name); 443 | // If the tile is already processed on edges we skip 444 | if(!tile.geometry.processed['allSides']){ 445 | if (!tile.geometry.processed['top']){ 446 | this.geometryEdgeTester(tile, neighbours['top'], 'top'); 447 | } 448 | if (!tile.geometry.processed['bottom']){ 449 | this.geometryEdgeTester(tile, neighbours['bottom'], 'bottom'); 450 | } 451 | if (!tile.geometry.processed['left']){ 452 | this.geometryEdgeTester(tile, neighbours['left'], 'left'); 453 | } 454 | if (!tile.geometry.processed['right']){ 455 | this.geometryEdgeTester(tile, neighbours['right'], 'right'); 456 | } 457 | } 458 | // Test if neighbours are loaded 459 | else 460 | { 461 | if (!tile.geometry.processed['topLeft']){ 462 | this.geometryCornerTester(tile, 463 | [ neighbours['topLeft'], 464 | neighbours['left'], 465 | neighbours['top'] 466 | ], 467 | [ 'topLeft', 468 | 'left', 469 | 'top' 470 | ]); 471 | } 472 | if (!tile.geometry.processed['bottomLeft']){ 473 | this.geometryCornerTester(tile, 474 | [ neighbours['bottomLeft'], 475 | neighbours['left'], 476 | neighbours['bottom'] 477 | ], 478 | [ 'bottomLeft', 479 | 'left', 480 | 'bottom' 481 | ]); 482 | } 483 | if (!tile.geometry.processed['bottomRight']){ 484 | this.geometryCornerTester(tile, 485 | [ neighbours['bottomRight'], 486 | neighbours['right'], 487 | neighbours['bottom'] 488 | ], 489 | [ 'bottomRight', 490 | 'right', 491 | 'bottom' 492 | ]); 493 | } 494 | if (!tile.geometry.processed['topRight']){ 495 | this.geometryCornerTester(tile, 496 | [ neighbours['topRight'], 497 | neighbours['right'], 498 | neighbours['top'] 499 | ], 500 | [ 'topRight', 501 | 'right', 502 | 'top' 503 | ]); 504 | } 505 | 506 | } 507 | }; 508 | 509 | ns.ThreeDMap.prototype.geometryEdgeTester = function (tile, neighbourName, placement) { 510 | var neighbour; //, tile; 511 | if (this.foregroundGroup.getObjectByName(neighbourName)) { 512 | neighbour = this.foregroundGroup.getObjectByName(neighbourName); 513 | if (neighbour.geometry.loaded == true && neighbour.scale.z >= 1) { 514 | if (tile.geometry.loaded == true) { 515 | this.geometryEdgeFixer(tile, neighbour, placement); 516 | } 517 | } 518 | 519 | } 520 | }; 521 | 522 | 523 | ns.ThreeDMap.prototype.geometryEdgeFixer = function (tile, neighbour, placement) { 524 | var i, oppositeEdge; 525 | // Edges 526 | oppositeEdge ={ 527 | top: 'bottom', 528 | bottom: 'top', 529 | left: 'right', 530 | right: 'left', 531 | topLeft: 'bottomRight', 532 | bottomRight: 'topLeft', 533 | topRight: 'bottomLeft', 534 | bottomLeft: 'topRight' 535 | } 536 | 537 | for (i = 0; i < this.edges[placement].length; i++) { 538 | tile.geometry.vertices[this.edges[placement][i]].z = (tile.geometry.vertices[this.edges[placement][i]].z + neighbour.geometry.vertices[this.edges[oppositeEdge[placement]][i]].z) / 2; 539 | neighbour.geometry.vertices[this.edges[oppositeEdge[placement]][i]].z = tile.geometry.vertices[this.edges[placement][i]].z; 540 | } 541 | tile.geometry.verticesNeedUpdate = true; 542 | neighbour.geometry.verticesNeedUpdate = true; 543 | tile.geometry.processed[placement] = true; 544 | neighbour.geometry.processed[oppositeEdge[placement]] = true; 545 | if (tile.geometry.processed['top'] & tile.geometry.processed['bottom'] & tile.geometry.processed['left'] & tile.geometry.processed['right'] ){ 546 | tile.geometry.processed['allSides'] = true; 547 | if ( tile.geometry.processed['topLeft'] & tile.geometry.processed['bottomLeft'] & tile.geometry.processed['bottomRight'] & tile.geometry.processed['topRight'] ) 548 | tile.geometry.processed['all'] = true; 549 | } 550 | if (neighbour.geometry.processed['top'] & neighbour.geometry.processed['bottom'] & neighbour.geometry.processed['left'] & neighbour.geometry.processed['right'] ){ 551 | neighbour.geometry.processed['allSides'] = true; 552 | if (neighbour.geometry.processed['topLeft'] & neighbour.geometry.processed['bottomLeft'] & neighbour.geometry.processed['bottomRight'] & neighbour.geometry.processed['topRight'] ) 553 | neighbour.geometry.processed['all'] = true; 554 | } 555 | }; 556 | 557 | 558 | ns.ThreeDMap.prototype.geometryCornerTester = function (tile, neighbourNames, placements) { 559 | var neighbour; //, tile; 560 | var neighbours=[]; 561 | if (tile.geometry.loaded == true) { 562 | for (var i in placements){ 563 | if (this.foregroundGroup.getObjectByName(neighbourNames[i])) { 564 | var neighbour=this.foregroundGroup.getObjectByName(neighbourNames[i]); 565 | // Populate array with neighbour 566 | if (neighbour.geometry.loaded == true && neighbour.scale.z >= 1) { 567 | neighbours.push(neighbour); 568 | } 569 | } 570 | } 571 | } 572 | // Check to see if we have all neighbours 573 | if(neighbours.length==3) 574 | this.geometryCornerFixer(tile, neighbours, placements); 575 | }; 576 | 577 | ns.ThreeDMap.prototype.geometryCornerFixer = function (tile, neighbours, placements) { 578 | // Index to invert corners 579 | // TODO: This is not very easy to read. Might need a better solution 580 | // TODO: This is constant and should be defined only once 581 | var oppositeCorners={ 582 | topLeft: { 583 | topLeft: 'bottomRight', 584 | left: 'topRight', 585 | top: 'bottomLeft' 586 | }, 587 | bottomRight: { 588 | bottomRight: 'topLeft', 589 | right: 'bottomLeft', 590 | bottom:'topRight' 591 | }, 592 | topRight: { 593 | topRight: 'bottomLeft', 594 | right: 'topLeft', 595 | top: 'bottomRight' 596 | }, 597 | bottomLeft: { 598 | bottomLeft: 'topRight', 599 | left: 'bottomRight', 600 | bottom: 'topLeft' 601 | } 602 | } 603 | 604 | // Calculate average height 605 | var averageHeightCorner= 606 | ( 607 | tile.geometry.vertices[this.edges[placements[0]]].z + 608 | neighbours[0].geometry.vertices[this.edges[ 609 | oppositeCorners[placements[0]][placements[0]] 610 | ]].z + 611 | neighbours[1].geometry.vertices[this.edges[ 612 | oppositeCorners[placements[0]][placements[1]] 613 | ]].z + 614 | neighbours[2].geometry.vertices[this.edges[ 615 | oppositeCorners[placements[0]][placements[2]] 616 | ]].z 617 | ) /4; 618 | 619 | // Set vertex on tile and neighbours to average value 620 | tile.geometry.vertices[this.edges[placements[0]]].z=averageHeightCorner; 621 | neighbours[0].geometry.vertices[this.edges[ 622 | oppositeCorners[placements[0]][placements[0]] 623 | ]].z=averageHeightCorner; 624 | neighbours[1].geometry.vertices[this.edges[ 625 | oppositeCorners[placements[0]][placements[1]] 626 | ]].z=averageHeightCorner; 627 | neighbours[2].geometry.vertices[this.edges[ 628 | oppositeCorners[placements[0]][placements[2]] 629 | ]].z=averageHeightCorner; 630 | 631 | // Flag for update 632 | tile.geometry.verticesNeedUpdate=true; 633 | neighbours[0].geometry.verticesNeedUpdate=true; 634 | neighbours[1].geometry.verticesNeedUpdate=true; 635 | neighbours[2].geometry.verticesNeedUpdate=true; 636 | 637 | // Flag corners as processed 638 | tile.geometry.processed[placements[0]]=true; 639 | neighbours[0].geometry.processed[oppositeCorners[placements[0]][placements[0]]]=true; 640 | neighbours[1].geometry.processed[oppositeCorners[placements[0]][placements[1]]]=true; 641 | neighbours[2].geometry.processed[oppositeCorners[placements[0]][placements[2]]]=true; 642 | 643 | // Check if all corners are averaged and flag if so 644 | if ( tile.geometry.processed['topLeft'] & tile.geometry.processed['bottomLeft'] & tile.geometry.processed['bottomRight'] & tile.geometry.processed['topRight'] ) 645 | tile.geometry.processed['all'] = true; 646 | for (var i =0; i<3;i++){ 647 | if (neighbours[i].geometry.processed['topLeft'] & neighbours[i].geometry.processed['bottomLeft'] & neighbours[i].geometry.processed['bottomRight'] & neighbours[i].geometry.processed['topRight'] ) 648 | neighbours[i].geometry.processed['all'] = true; 649 | } 650 | } 651 | }(wxs3)); 652 | -------------------------------------------------------------------------------- /three/TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | */ 4 | 5 | THREE.TrackballControls = function ( object, domElement ) { 6 | 7 | var _this = this; 8 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 }; 9 | 10 | this.object = object; 11 | this.domElement = ( domElement !== undefined ) ? domElement : document; 12 | 13 | // API 14 | 15 | this.enabled = true; 16 | 17 | this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 }; 18 | this.radius = ( this.screen.width + this.screen.height ) / 4; 19 | 20 | this.rotateSpeed = 1.0; 21 | this.zoomSpeed = 1.2; 22 | this.panSpeed = 0.3; 23 | 24 | this.noRotate = false; 25 | this.noZoom = false; 26 | this.noPan = false; 27 | 28 | this.staticMoving = false; 29 | this.dynamicDampingFactor = 0.2; 30 | 31 | this.minDistance = 0; 32 | this.maxDistance = Infinity; 33 | 34 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 35 | 36 | // internals 37 | 38 | this.target = new THREE.Vector3(); 39 | 40 | var lastPosition = new THREE.Vector3(); 41 | 42 | var _state = STATE.NONE, 43 | _prevState = STATE.NONE, 44 | 45 | _eye = new THREE.Vector3(), 46 | 47 | _rotateStart = new THREE.Vector3(), 48 | _rotateEnd = new THREE.Vector3(), 49 | 50 | _zoomStart = new THREE.Vector2(), 51 | _zoomEnd = new THREE.Vector2(), 52 | 53 | _touchZoomDistanceStart = 0, 54 | _touchZoomDistanceEnd = 0, 55 | 56 | _panStart = new THREE.Vector2(), 57 | _panEnd = new THREE.Vector2(); 58 | 59 | // for reset 60 | 61 | this.target0 = this.target.clone(); 62 | this.position0 = this.object.position.clone(); 63 | this.up0 = this.object.up.clone(); 64 | 65 | // events 66 | 67 | var changeEvent = { type: 'change' }; 68 | 69 | 70 | // methods 71 | 72 | this.handleResize = function () { 73 | 74 | this.screen.width = window.innerWidth; 75 | this.screen.height = window.innerHeight; 76 | 77 | this.screen.offsetLeft = 0; 78 | this.screen.offsetTop = 0; 79 | 80 | this.radius = ( this.screen.width + this.screen.height ) / 4; 81 | 82 | }; 83 | 84 | this.handleEvent = function ( event ) { 85 | 86 | if ( typeof this[ event.type ] == 'function' ) { 87 | 88 | this[ event.type ]( event ); 89 | 90 | } 91 | 92 | }; 93 | 94 | this.getMouseOnScreen = function ( clientX, clientY ) { 95 | 96 | return new THREE.Vector2( 97 | ( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5, 98 | ( clientY - _this.screen.offsetTop ) / _this.radius * 0.5 99 | ); 100 | 101 | }; 102 | 103 | this.getMouseProjectionOnBall = function ( clientX, clientY ) { 104 | 105 | var mouseOnBall = new THREE.Vector3( 106 | ( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius, 107 | ( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius, 108 | 0.0 109 | ); 110 | 111 | var length = mouseOnBall.length(); 112 | 113 | if ( length > 1.0 ) { 114 | 115 | mouseOnBall.normalize(); 116 | 117 | } else { 118 | 119 | mouseOnBall.z = Math.sqrt( 1.0 - length * length ); 120 | 121 | } 122 | 123 | _eye.copy( _this.object.position ).sub( _this.target ); 124 | 125 | var projection = _this.object.up.clone().setLength( mouseOnBall.y ); 126 | projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) ); 127 | projection.add( _eye.setLength( mouseOnBall.z ) ); 128 | 129 | return projection; 130 | 131 | }; 132 | 133 | this.rotateCamera = function () { 134 | 135 | var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() ); 136 | 137 | if ( angle ) { 138 | 139 | var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(); 140 | quaternion = new THREE.Quaternion(); 141 | 142 | angle *= _this.rotateSpeed; 143 | 144 | quaternion.setFromAxisAngle( axis, -angle ); 145 | 146 | _eye.applyQuaternion( quaternion ); 147 | _this.object.up.applyQuaternion( quaternion ); 148 | 149 | _rotateEnd.applyQuaternion( quaternion ); 150 | 151 | if ( _this.staticMoving ) { 152 | 153 | _rotateStart.copy( _rotateEnd ); 154 | 155 | } else { 156 | 157 | quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) ); 158 | _rotateStart.applyQuaternion( quaternion ); 159 | 160 | } 161 | 162 | } 163 | 164 | }; 165 | 166 | this.zoomCamera = function () { 167 | 168 | if ( _state === STATE.TOUCH_ZOOM ) { 169 | 170 | var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 171 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 172 | _eye.multiplyScalar( factor ); 173 | 174 | } else { 175 | 176 | var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 177 | 178 | if ( factor !== 1.0 && factor > 0.0 ) { 179 | 180 | _eye.multiplyScalar( factor ); 181 | 182 | if ( _this.staticMoving ) { 183 | 184 | _zoomStart.copy( _zoomEnd ); 185 | 186 | } else { 187 | 188 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 189 | 190 | } 191 | 192 | } 193 | 194 | } 195 | 196 | }; 197 | 198 | this.panCamera = function () { 199 | 200 | var mouseChange = _panEnd.clone().sub( _panStart ); 201 | 202 | if ( mouseChange.lengthSq() ) { 203 | 204 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 205 | 206 | var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x ); 207 | pan.add( _this.object.up.clone().setLength( mouseChange.y ) ); 208 | 209 | _this.object.position.add( pan ); 210 | _this.target.add( pan ); 211 | 212 | if ( _this.staticMoving ) { 213 | 214 | _panStart = _panEnd; 215 | 216 | } else { 217 | 218 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 219 | 220 | } 221 | 222 | } 223 | 224 | }; 225 | 226 | this.checkDistances = function () { 227 | 228 | if ( !_this.noZoom || !_this.noPan ) { 229 | 230 | if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) { 231 | 232 | _this.object.position.setLength( _this.maxDistance ); 233 | 234 | } 235 | 236 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 237 | 238 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 239 | 240 | } 241 | 242 | } 243 | 244 | }; 245 | 246 | this.update = function () { 247 | 248 | _eye.subVectors( _this.object.position, _this.target ); 249 | 250 | if ( !_this.noRotate ) { 251 | 252 | _this.rotateCamera(); 253 | 254 | } 255 | 256 | if ( !_this.noZoom ) { 257 | 258 | _this.zoomCamera(); 259 | 260 | } 261 | 262 | if ( !_this.noPan ) { 263 | 264 | _this.panCamera(); 265 | 266 | } 267 | 268 | _this.object.position.addVectors( _this.target, _eye ); 269 | 270 | _this.checkDistances(); 271 | 272 | _this.object.lookAt( _this.target ); 273 | 274 | if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) { 275 | 276 | _this.dispatchEvent( changeEvent ); 277 | 278 | lastPosition.copy( _this.object.position ); 279 | 280 | } 281 | 282 | }; 283 | 284 | this.reset = function () { 285 | 286 | _state = STATE.NONE; 287 | _prevState = STATE.NONE; 288 | 289 | _this.target.copy( _this.target0 ); 290 | _this.object.position.copy( _this.position0 ); 291 | _this.object.up.copy( _this.up0 ); 292 | 293 | _eye.subVectors( _this.object.position, _this.target ); 294 | 295 | _this.object.lookAt( _this.target ); 296 | 297 | _this.dispatchEvent( changeEvent ); 298 | 299 | lastPosition.copy( _this.object.position ); 300 | 301 | }; 302 | 303 | // listeners 304 | 305 | function keydown( event ) { 306 | 307 | if ( _this.enabled === false ) return; 308 | 309 | window.removeEventListener( 'keydown', keydown ); 310 | 311 | _prevState = _state; 312 | 313 | if ( _state !== STATE.NONE ) { 314 | 315 | return; 316 | 317 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) { 318 | 319 | _state = STATE.ROTATE; 320 | 321 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) { 322 | 323 | _state = STATE.ZOOM; 324 | 325 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) { 326 | 327 | _state = STATE.PAN; 328 | 329 | } 330 | 331 | } 332 | 333 | function keyup( event ) { 334 | 335 | if ( _this.enabled === false ) return; 336 | 337 | _state = _prevState; 338 | 339 | window.addEventListener( 'keydown', keydown, false ); 340 | 341 | } 342 | 343 | function mousedown( event ) { 344 | 345 | if ( _this.enabled === false ) return; 346 | 347 | event.preventDefault(); 348 | event.stopPropagation(); 349 | 350 | if ( _state === STATE.NONE ) { 351 | 352 | _state = event.button; 353 | 354 | } 355 | 356 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 357 | 358 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY ); 359 | 360 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 361 | 362 | _zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 363 | 364 | } else if ( _state === STATE.PAN && !_this.noPan ) { 365 | 366 | _panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 367 | 368 | } 369 | 370 | document.addEventListener( 'mousemove', mousemove, false ); 371 | document.addEventListener( 'mouseup', mouseup, false ); 372 | 373 | } 374 | 375 | function mousemove( event ) { 376 | 377 | if ( _this.enabled === false ) return; 378 | 379 | event.preventDefault(); 380 | event.stopPropagation(); 381 | 382 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 383 | 384 | _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY ); 385 | 386 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 387 | 388 | _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 389 | 390 | } else if ( _state === STATE.PAN && !_this.noPan ) { 391 | 392 | _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 393 | 394 | } 395 | 396 | } 397 | 398 | function mouseup( event ) { 399 | 400 | if ( _this.enabled === false ) return; 401 | 402 | event.preventDefault(); 403 | event.stopPropagation(); 404 | 405 | _state = STATE.NONE; 406 | 407 | document.removeEventListener( 'mousemove', mousemove ); 408 | document.removeEventListener( 'mouseup', mouseup ); 409 | 410 | } 411 | 412 | function mousewheel( event ) { 413 | 414 | if ( _this.enabled === false ) return; 415 | 416 | event.preventDefault(); 417 | event.stopPropagation(); 418 | 419 | var delta = 0; 420 | 421 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 422 | 423 | delta = event.wheelDelta / 40; 424 | 425 | } else if ( event.detail ) { // Firefox 426 | 427 | delta = - event.detail / 3; 428 | 429 | } 430 | 431 | _zoomStart.y += delta * 0.01; 432 | 433 | } 434 | 435 | function touchstart( event ) { 436 | 437 | if ( _this.enabled === false ) return; 438 | 439 | switch ( event.touches.length ) { 440 | 441 | case 1: 442 | _state = STATE.TOUCH_ROTATE; 443 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 444 | break; 445 | 446 | case 2: 447 | _state = STATE.TOUCH_ZOOM; 448 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 449 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 450 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 451 | break; 452 | 453 | case 3: 454 | _state = STATE.TOUCH_PAN; 455 | _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 456 | break; 457 | 458 | default: 459 | _state = STATE.NONE; 460 | 461 | } 462 | 463 | } 464 | 465 | function touchmove( event ) { 466 | 467 | if ( _this.enabled === false ) return; 468 | 469 | event.preventDefault(); 470 | event.stopPropagation(); 471 | 472 | switch ( event.touches.length ) { 473 | 474 | case 1: 475 | _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 476 | break; 477 | 478 | case 2: 479 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 480 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 481 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ) 482 | break; 483 | 484 | case 3: 485 | _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 486 | break; 487 | 488 | default: 489 | _state = STATE.NONE; 490 | 491 | } 492 | 493 | } 494 | 495 | function touchend( event ) { 496 | 497 | if ( _this.enabled === false ) return; 498 | 499 | switch ( event.touches.length ) { 500 | 501 | case 1: 502 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 503 | break; 504 | 505 | case 2: 506 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; 507 | break; 508 | 509 | case 3: 510 | _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 511 | break; 512 | 513 | } 514 | 515 | _state = STATE.NONE; 516 | 517 | } 518 | 519 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 520 | 521 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 522 | 523 | this.domElement.addEventListener( 'mousewheel', mousewheel, false ); 524 | this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox 525 | 526 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 527 | this.domElement.addEventListener( 'touchend', touchend, false ); 528 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 529 | 530 | window.addEventListener( 'keydown', keydown, false ); 531 | window.addEventListener( 'keyup', keyup, false ); 532 | 533 | this.handleResize(); 534 | 535 | }; 536 | 537 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); -------------------------------------------------------------------------------- /tiff-js/tiff.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | "use strict"; 6 | 7 | function TIFFParser() { 8 | this.tiffDataView = undefined; 9 | this.littleEndian = undefined; 10 | this.fileDirectories = []; 11 | }; 12 | 13 | TIFFParser.prototype = { 14 | isLittleEndian: function () { 15 | // Get byte order mark. 16 | var BOM = this.getBytes(2, 0); 17 | 18 | // Find out the endianness. 19 | if (BOM === 0x4949) { 20 | this.littleEndian = true; 21 | } else if (BOM === 0x4D4D) { 22 | this.littleEndian = false; 23 | } else { 24 | console.log( BOM ); 25 | throw TypeError("Invalid byte order value."); 26 | } 27 | 28 | return this.littleEndian; 29 | }, 30 | 31 | hasTowel: function () { 32 | // Check for towel. 33 | if (this.getBytes(2, 2) !== 42) { 34 | throw RangeError("You forgot your towel!"); 35 | return false; 36 | } 37 | 38 | return true; 39 | }, 40 | 41 | getFieldTagName: function (fieldTag) { 42 | // See: http://www.digitizationguidelines.gov/guidelines/TIFF_Metadata_Final.pdf 43 | // See: http://www.digitalpreservation.gov/formats/content/tiff_tags.shtml 44 | var fieldTagNames = { 45 | // TIFF Baseline 46 | 0x013B: 'Artist', 47 | 0x0102: 'BitsPerSample', 48 | 0x0109: 'CellLength', 49 | 0x0108: 'CellWidth', 50 | 0x0140: 'ColorMap', 51 | 0x0103: 'Compression', 52 | 0x8298: 'Copyright', 53 | 0x0132: 'DateTime', 54 | 0x0152: 'ExtraSamples', 55 | 0x010A: 'FillOrder', 56 | 0x0121: 'FreeByteCounts', 57 | 0x0120: 'FreeOffsets', 58 | 0x0123: 'GrayResponseCurve', 59 | 0x0122: 'GrayResponseUnit', 60 | 0x013C: 'HostComputer', 61 | 0x010E: 'ImageDescription', 62 | 0x0101: 'ImageLength', 63 | 0x0100: 'ImageWidth', 64 | 0x010F: 'Make', 65 | 0x0119: 'MaxSampleValue', 66 | 0x0118: 'MinSampleValue', 67 | 0x0110: 'Model', 68 | 0x00FE: 'NewSubfileType', 69 | 0x0112: 'Orientation', 70 | 0x0106: 'PhotometricInterpretation', 71 | 0x011C: 'PlanarConfiguration', 72 | 0x0128: 'ResolutionUnit', 73 | 0x0116: 'RowsPerStrip', 74 | 0x0115: 'SamplesPerPixel', 75 | 0x0131: 'Software', 76 | 0x0117: 'StripByteCounts', 77 | 0x0111: 'StripOffsets', 78 | 0x00FF: 'SubfileType', 79 | 0x0107: 'Threshholding', 80 | 0x011A: 'XResolution', 81 | 0x011B: 'YResolution', 82 | 83 | // TIFF Extended 84 | 0x0146: 'BadFaxLines', 85 | 0x0147: 'CleanFaxData', 86 | 0x0157: 'ClipPath', 87 | 0x0148: 'ConsecutiveBadFaxLines', 88 | 0x01B1: 'Decode', 89 | 0x01B2: 'DefaultImageColor', 90 | 0x010D: 'DocumentName', 91 | 0x0150: 'DotRange', 92 | 0x0141: 'HalftoneHints', 93 | 0x015A: 'Indexed', 94 | 0x015B: 'JPEGTables', 95 | 0x011D: 'PageName', 96 | 0x0129: 'PageNumber', 97 | 0x013D: 'Predictor', 98 | 0x013F: 'PrimaryChromaticities', 99 | 0x0214: 'ReferenceBlackWhite', 100 | 0x0153: 'SampleFormat', 101 | 0x022F: 'StripRowCounts', 102 | 0x014A: 'SubIFDs', 103 | 0x0124: 'T4Options', 104 | 0x0125: 'T6Options', 105 | 0x0145: 'TileByteCounts', 106 | 0x0143: 'TileLength', 107 | 0x0144: 'TileOffsets', 108 | 0x0142: 'TileWidth', 109 | 0x012D: 'TransferFunction', 110 | 0x013E: 'WhitePoint', 111 | 0x0158: 'XClipPathUnits', 112 | 0x011E: 'XPosition', 113 | 0x0211: 'YCbCrCoefficients', 114 | 0x0213: 'YCbCrPositioning', 115 | 0x0212: 'YCbCrSubSampling', 116 | 0x0159: 'YClipPathUnits', 117 | 0x011F: 'YPosition', 118 | 119 | // EXIF 120 | 0x9202: 'ApertureValue', 121 | 0xA001: 'ColorSpace', 122 | 0x9004: 'DateTimeDigitized', 123 | 0x9003: 'DateTimeOriginal', 124 | 0x8769: 'Exif IFD', 125 | 0x9000: 'ExifVersion', 126 | 0x829A: 'ExposureTime', 127 | 0xA300: 'FileSource', 128 | 0x9209: 'Flash', 129 | 0xA000: 'FlashpixVersion', 130 | 0x829D: 'FNumber', 131 | 0xA420: 'ImageUniqueID', 132 | 0x9208: 'LightSource', 133 | 0x927C: 'MakerNote', 134 | 0x9201: 'ShutterSpeedValue', 135 | 0x9286: 'UserComment', 136 | 137 | // IPTC 138 | 0x83BB: 'IPTC', 139 | 140 | // ICC 141 | 0x8773: 'ICC Profile', 142 | 143 | // XMP 144 | 0x02BC: 'XMP', 145 | 146 | // GDAL 147 | 0xA480: 'GDAL_METADATA', 148 | 0xA481: 'GDAL_NODATA', 149 | 150 | // Photoshop 151 | 0x8649: 'Photoshop', 152 | }; 153 | 154 | var fieldTagName; 155 | 156 | if (fieldTag in fieldTagNames) { 157 | fieldTagName = fieldTagNames[fieldTag]; 158 | } else { 159 | //console.log( "Unknown Field Tag:", fieldTag); 160 | fieldTagName = "Tag" + fieldTag; 161 | } 162 | 163 | return fieldTagName; 164 | }, 165 | 166 | getFieldTypeName: function (fieldType) { 167 | var fieldTypeNames = { 168 | 0x0001: 'BYTE', 169 | 0x0002: 'ASCII', 170 | 0x0003: 'SHORT', 171 | 0x0004: 'LONG', 172 | 0x0005: 'RATIONAL', 173 | 0x0006: 'SBYTE', 174 | 0x0007: 'UNDEFINED', 175 | 0x0008: 'SSHORT', 176 | 0x0009: 'SLONG', 177 | 0x000A: 'SRATIONAL', 178 | 0x000B: 'FLOAT', 179 | 0x000C: 'DOUBLE', 180 | }; 181 | 182 | var fieldTypeName; 183 | 184 | if (fieldType in fieldTypeNames) { 185 | fieldTypeName = fieldTypeNames[fieldType]; 186 | } 187 | 188 | return fieldTypeName; 189 | }, 190 | 191 | getFieldTypeLength: function (fieldTypeName) { 192 | var fieldTypeLength; 193 | 194 | if (['BYTE', 'ASCII', 'SBYTE', 'UNDEFINED'].indexOf(fieldTypeName) !== -1) { 195 | fieldTypeLength = 1; 196 | } else if (['SHORT', 'SSHORT'].indexOf(fieldTypeName) !== -1) { 197 | fieldTypeLength = 2; 198 | } else if (['LONG', 'SLONG', 'FLOAT'].indexOf(fieldTypeName) !== -1) { 199 | fieldTypeLength = 4; 200 | } else if (['RATIONAL', 'SRATIONAL', 'DOUBLE'].indexOf(fieldTypeName) !== -1) { 201 | fieldTypeLength = 8; 202 | } 203 | 204 | return fieldTypeLength; 205 | }, 206 | 207 | getBits: function (numBits, byteOffset, bitOffset) { 208 | bitOffset = bitOffset || 0; 209 | var extraBytes = Math.floor(bitOffset / 8); 210 | var newByteOffset = byteOffset + extraBytes; 211 | var totalBits = bitOffset + numBits; 212 | var shiftRight = 32 - numBits; 213 | 214 | if (totalBits <= 0) { 215 | console.log( numBits, byteOffset, bitOffset ); 216 | throw RangeError("No bits requested"); 217 | } else if (totalBits <= 8) { 218 | var shiftLeft = 24 + bitOffset; 219 | var rawBits = this.tiffDataView.getUint8(newByteOffset, this.littleEndian); 220 | } else if (totalBits <= 16) { 221 | var shiftLeft = 16 + bitOffset; 222 | var rawBits = this.tiffDataView.getUint16(newByteOffset, this.littleEndian); 223 | } else if (totalBits <= 32) { 224 | var shiftLeft = bitOffset; 225 | var rawBits = this.tiffDataView.getUint32(newByteOffset, this.littleEndian); 226 | } else { 227 | console.log( numBits, byteOffset, bitOffset ); 228 | throw RangeError("Too many bits requested"); 229 | } 230 | 231 | var chunkInfo = { 232 | 'bits': ((rawBits << shiftLeft) >>> shiftRight), 233 | 'byteOffset': newByteOffset + Math.floor(totalBits / 8), 234 | 'bitOffset': totalBits % 8, 235 | }; 236 | 237 | return chunkInfo; 238 | }, 239 | 240 | getBytes: function (numBytes, offset) { 241 | if (numBytes <= 0) { 242 | console.log( numBytes, offset ); 243 | throw RangeError("No bytes requested"); 244 | } else if (numBytes <= 1) { 245 | return this.tiffDataView.getUint8(offset, this.littleEndian); 246 | } else if (numBytes <= 2) { 247 | return this.tiffDataView.getUint16(offset, this.littleEndian); 248 | } else if (numBytes <= 3) { 249 | return this.tiffDataView.getUint32(offset, this.littleEndian) >>> 8; 250 | } else if (numBytes <= 4) { 251 | return this.tiffDataView.getUint32(offset, this.littleEndian); 252 | //return this.tiffDataView.getFloat32(offset, this.littleEndian); 253 | } else { 254 | console.log( numBytes, offset ); 255 | throw RangeError("Too many bytes requested"); 256 | } 257 | }, 258 | 259 | getFieldValues: function (fieldTagName, fieldTypeName, typeCount, valueOffset) { 260 | var fieldValues = []; 261 | 262 | var fieldTypeLength = this.getFieldTypeLength(fieldTypeName); 263 | var fieldValueSize = fieldTypeLength * typeCount; 264 | 265 | if (fieldValueSize <= 4) { 266 | // The value is stored at the big end of the valueOffset. 267 | if (this.littleEndian === false) { 268 | var value = valueOffset >>> ((4 - fieldTypeLength) * 8); 269 | } else { 270 | var value = valueOffset; 271 | } 272 | 273 | fieldValues.push(value); 274 | } else { 275 | for (var i = 0; i < typeCount; i++) { 276 | var indexOffset = fieldTypeLength * i; 277 | 278 | if (fieldTypeLength >= 8) { 279 | if (['RATIONAL', 'SRATIONAL'].indexOf(fieldTypeName) !== -1) { 280 | // Numerator 281 | fieldValues.push(this.getBytes(4, valueOffset + indexOffset)); 282 | // Denominator 283 | fieldValues.push(this.getBytes(4, valueOffset + indexOffset + 4)); 284 | } else if (['DOUBLE'].indexOf(fieldTypeName) !== -1) { 285 | fieldValues.push(this.getBytes(4, valueOffset + indexOffset) + this.getBytes(4, valueOffset + indexOffset + 4)); 286 | } else { 287 | console.log( fieldTypeName, typeCount, fieldValueSize ); 288 | throw TypeError("Can't handle this field type or size"); 289 | } 290 | } else { 291 | fieldValues.push(this.getBytes(fieldTypeLength, valueOffset + indexOffset)); 292 | } 293 | } 294 | } 295 | 296 | if (fieldTypeName === 'ASCII') { 297 | fieldValues.forEach(function(e, i, a) { a[i] = String.fromCharCode(e); }); 298 | } 299 | 300 | return fieldValues; 301 | }, 302 | 303 | clampColorSample: function(colorSample, bitsPerSample) { 304 | var multiplier = Math.pow(2, 8 - bitsPerSample); 305 | 306 | return Math.floor((colorSample * multiplier) + (multiplier - 1)); 307 | }, 308 | 309 | makeRGBAFillValue: function(r, g, b, a) { 310 | if(typeof a === 'undefined') { 311 | a = 1.0; 312 | } 313 | return "rgba(" + r + ", " + g + ", " + b + ", " + a + ")"; 314 | }, 315 | 316 | parseFileDirectory: function (byteOffset) { 317 | var numDirEntries = this.getBytes(2, byteOffset); 318 | 319 | var tiffFields = []; 320 | 321 | for (var i = byteOffset + 2, entryCount = 0; entryCount < numDirEntries; i += 12, entryCount++) { 322 | var fieldTag = this.getBytes(2, i); 323 | var fieldType = this.getBytes(2, i + 2); 324 | var typeCount = this.getBytes(4, i + 4); 325 | var valueOffset = this.getBytes(4, i + 8); 326 | 327 | var fieldTagName = this.getFieldTagName( fieldTag ); 328 | var fieldTypeName = this.getFieldTypeName( fieldType ); 329 | 330 | var fieldValues = this.getFieldValues(fieldTagName, fieldTypeName, typeCount, valueOffset); 331 | 332 | tiffFields[fieldTagName] = { 'type': fieldTypeName, 'values': fieldValues }; 333 | } 334 | 335 | this.fileDirectories.push( tiffFields ); 336 | 337 | var nextIFDByteOffset = this.getBytes(4, i); 338 | 339 | if (nextIFDByteOffset === 0x00000000) { 340 | return this.fileDirectories; 341 | } else { 342 | return this.parseFileDirectory(nextIFDByteOffset); 343 | } 344 | }, 345 | 346 | parseTIFF: function (tiffArrayBuffer, canvas) { 347 | canvas = canvas || document.createElement('canvas'); 348 | 349 | this.tiffDataView = new DataView(tiffArrayBuffer); 350 | this.canvas = canvas; 351 | 352 | this.littleEndian = this.isLittleEndian(this.tiffDataView); 353 | 354 | if (!this.hasTowel(this.tiffDataView, this.littleEndian)) { 355 | return; 356 | } 357 | 358 | var firstIFDByteOffset = this.getBytes(4, 4); 359 | 360 | this.fileDirectories = this.parseFileDirectory(firstIFDByteOffset); 361 | 362 | var fileDirectory = this.fileDirectories[0]; 363 | 364 | //console.log( fileDirectory ); 365 | 366 | var imageWidth = fileDirectory.ImageWidth.values[0]; 367 | var imageLength = fileDirectory.ImageLength.values[0]; 368 | 369 | this.canvas.width = imageWidth; 370 | this.canvas.height = imageLength; 371 | 372 | var strips = []; 373 | 374 | var compression = (fileDirectory.Compression) ? fileDirectory.Compression.values[0] : 1; 375 | 376 | var samplesPerPixel = fileDirectory.SamplesPerPixel.values[0]; 377 | 378 | var sampleProperties = []; 379 | 380 | var bitsPerPixel = 0; 381 | var hasBytesPerPixel = false; 382 | 383 | fileDirectory.BitsPerSample.values.forEach(function(bitsPerSample, i, bitsPerSampleValues) { 384 | sampleProperties[i] = { 385 | 'bitsPerSample': bitsPerSample, 386 | 'hasBytesPerSample': false, 387 | 'bytesPerSample': undefined, 388 | }; 389 | 390 | if ((bitsPerSample % 8) === 0) { 391 | sampleProperties[i].hasBytesPerSample = true; 392 | sampleProperties[i].bytesPerSample = bitsPerSample / 8; 393 | } 394 | 395 | bitsPerPixel += bitsPerSample; 396 | }, this); 397 | 398 | if ((bitsPerPixel % 8) === 0) { 399 | hasBytesPerPixel = true; 400 | var bytesPerPixel = bitsPerPixel / 8; 401 | } 402 | 403 | var stripOffsetValues = fileDirectory.StripOffsets.values; 404 | var numStripOffsetValues = stripOffsetValues.length; 405 | 406 | // StripByteCounts is supposed to be required, but see if we can recover anyway. 407 | if (fileDirectory.StripByteCounts) { 408 | var stripByteCountValues = fileDirectory.StripByteCounts.values; 409 | } else { 410 | console.log("Missing StripByteCounts!"); 411 | 412 | // Infer StripByteCounts, if possible. 413 | if (numStripOffsetValues === 1) { 414 | var stripByteCountValues = [Math.ceil((imageWidth * imageLength * bitsPerPixel) / 8)]; 415 | } else { 416 | throw Error("Cannot recover from missing StripByteCounts"); 417 | } 418 | } 419 | 420 | // Loop through strips and decompress as necessary. 421 | for (var i = 0; i < numStripOffsetValues; i++) { 422 | var stripOffset = stripOffsetValues[i]; 423 | strips[i] = []; 424 | 425 | var stripByteCount = stripByteCountValues[i]; 426 | 427 | // Loop through pixels. 428 | for (var byteOffset = 0, bitOffset = 0, jIncrement = 1, getHeader = true, pixel = [], numBytes = 0, sample = 0, currentSample = 0; byteOffset < stripByteCount; byteOffset += jIncrement) { 429 | // Decompress strip. 430 | switch (compression) { 431 | // Uncompressed 432 | case 1: 433 | // Loop through samples (sub-pixels). 434 | for (var m = 0, pixel = []; m < samplesPerPixel; m++) { 435 | if (sampleProperties[m].hasBytesPerSample) { 436 | // XXX: This is wrong! 437 | var sampleOffset = sampleProperties[m].bytesPerSample * m; 438 | 439 | //pixel.push(this.getBytes(sampleProperties[m].bytesPerSample, stripOffset + byteOffset + sampleOffset)); 440 | pixel.push(this.tiffDataView.getFloat32(stripOffset + byteOffset + sampleOffset, this.littleEndian )); 441 | } else { 442 | var sampleInfo = this.getBits(sampleProperties[m].bitsPerSample, stripOffset + byteOffset, bitOffset); 443 | 444 | pixel.push(sampleInfo.bits); 445 | 446 | byteOffset = sampleInfo.byteOffset - stripOffset; 447 | bitOffset = sampleInfo.bitOffset; 448 | 449 | throw RangeError("Cannot handle sub-byte bits per sample"); 450 | } 451 | } 452 | 453 | strips[i].push(pixel); 454 | 455 | if (hasBytesPerPixel) { 456 | jIncrement = bytesPerPixel; 457 | } else { 458 | jIncrement = 0; 459 | 460 | throw RangeError("Cannot handle sub-byte bits per pixel"); 461 | } 462 | break; 463 | 464 | // CITT Group 3 1-Dimensional Modified Huffman run-length encoding 465 | case 2: 466 | // XXX: Use PDF.js code? 467 | break; 468 | 469 | // Group 3 Fax 470 | case 3: 471 | // XXX: Use PDF.js code? 472 | break; 473 | 474 | // Group 4 Fax 475 | case 4: 476 | // XXX: Use PDF.js code? 477 | break; 478 | 479 | // LZW 480 | case 5: 481 | // XXX: Use PDF.js code? 482 | break; 483 | 484 | // Old-style JPEG (TIFF 6.0) 485 | case 6: 486 | // XXX: Use PDF.js code? 487 | break; 488 | 489 | // New-style JPEG (TIFF Specification Supplement 2) 490 | case 7: 491 | // XXX: Use PDF.js code? 492 | break; 493 | 494 | // PackBits 495 | case 32773: 496 | // Are we ready for a new block? 497 | if (getHeader) { 498 | getHeader = false; 499 | 500 | var blockLength = 1; 501 | var iterations = 1; 502 | 503 | // The header byte is signed. 504 | var header = this.tiffDataView.getInt8(stripOffset + byteOffset, this.littleEndian); 505 | 506 | if ((header >= 0) && (header <= 127)) { // Normal pixels. 507 | blockLength = header + 1; 508 | } else if ((header >= -127) && (header <= -1)) { // Collapsed pixels. 509 | iterations = -header + 1; 510 | } else /*if (header === -128)*/ { // Placeholder byte? 511 | getHeader = true; 512 | } 513 | } else { 514 | var currentByte = this.getBytes(1, stripOffset + byteOffset); 515 | 516 | // Duplicate bytes, if necessary. 517 | for (var m = 0; m < iterations; m++) { 518 | if (sampleProperties[sample].hasBytesPerSample) { 519 | // We're reading one byte at a time, so we need to handle multi-byte samples. 520 | currentSample = (currentSample << (8 * numBytes)) | currentByte; 521 | numBytes++; 522 | 523 | // Is our sample complete? 524 | if (numBytes === sampleProperties[sample].bytesPerSample) { 525 | pixel.push(currentSample); 526 | currentSample = numBytes = 0; 527 | sample++; 528 | } 529 | } else { 530 | throw RangeError("Cannot handle sub-byte bits per sample"); 531 | } 532 | 533 | // Is our pixel complete? 534 | if (sample === samplesPerPixel) 535 | { 536 | strips[i].push(pixel); 537 | 538 | pixel = []; 539 | sample = 0; 540 | } 541 | } 542 | 543 | blockLength--; 544 | 545 | // Is our block complete? 546 | if (blockLength === 0) { 547 | getHeader = true; 548 | } 549 | } 550 | 551 | jIncrement = 1; 552 | break; 553 | 554 | // Unknown compression algorithm 555 | default: 556 | // Do not attempt to parse the image data. 557 | break; 558 | } 559 | } 560 | 561 | // console.log( strips[i] ); 562 | } 563 | 564 | //console.log( strips ); 565 | return strips; 566 | 567 | if (canvas.getContext) { 568 | var ctx = this.canvas.getContext("2d"); 569 | 570 | // Set a default fill style. 571 | ctx.fillStyle = this.makeRGBAFillValue(255, 255, 255, 0); 572 | 573 | // If RowsPerStrip is missing, the whole image is in one strip. 574 | if (fileDirectory.RowsPerStrip) { 575 | var rowsPerStrip = fileDirectory.RowsPerStrip.values[0]; 576 | } else { 577 | var rowsPerStrip = imageLength; 578 | } 579 | 580 | var numStrips = strips.length; 581 | 582 | var imageLengthModRowsPerStrip = imageLength % rowsPerStrip; 583 | var rowsInLastStrip = (imageLengthModRowsPerStrip === 0) ? rowsPerStrip : imageLengthModRowsPerStrip; 584 | 585 | var numRowsInStrip = rowsPerStrip; 586 | var numRowsInPreviousStrip = 0; 587 | 588 | var photometricInterpretation = fileDirectory.PhotometricInterpretation.values[0]; 589 | 590 | var extraSamplesValues = []; 591 | var numExtraSamples = 0; 592 | 593 | if (fileDirectory.ExtraSamples) { 594 | extraSamplesValues = fileDirectory.ExtraSamples.values; 595 | numExtraSamples = extraSamplesValues.length; 596 | } 597 | 598 | if (fileDirectory.ColorMap) { 599 | var colorMapValues = fileDirectory.ColorMap.values; 600 | var colorMapSampleSize = Math.pow(2, sampleProperties[0].bitsPerSample); 601 | } 602 | 603 | // Loop through the strips in the image. 604 | for (var i = 0; i < numStrips; i++) { 605 | // The last strip may be short. 606 | if ((i + 1) === numStrips) { 607 | numRowsInStrip = rowsInLastStrip; 608 | } 609 | 610 | var numPixels = strips[i].length; 611 | var yPadding = numRowsInPreviousStrip * i; 612 | 613 | // Loop through the rows in the strip. 614 | for (var y = 0, j = 0; y < numRowsInStrip, j < numPixels; y++) { 615 | // Loop through the pixels in the row. 616 | for (var x = 0; x < imageWidth; x++, j++) { 617 | var pixelSamples = strips[i][j]; 618 | 619 | var red = 0; 620 | var green = 0; 621 | var blue = 0; 622 | var opacity = 1.0; 623 | 624 | if (numExtraSamples > 0) { 625 | for (var k = 0; k < numExtraSamples; k++) { 626 | if (extraSamplesValues[k] === 1 || extraSamplesValues[k] === 2) { 627 | // Clamp opacity to the range [0,1]. 628 | opacity = pixelSamples[3 + k] / 256; 629 | 630 | break; 631 | } 632 | } 633 | } 634 | 635 | switch (photometricInterpretation) { 636 | // Bilevel or Grayscale 637 | // WhiteIsZero 638 | case 0: 639 | if (sampleProperties[0].hasBytesPerSample) { 640 | var invertValue = Math.pow(0x10, sampleProperties[0].bytesPerSample * 2); 641 | } 642 | 643 | // Invert samples. 644 | pixelSamples.forEach(function(sample, index, samples) { samples[index] = invertValue - sample; }); 645 | 646 | // Bilevel or Grayscale 647 | // BlackIsZero 648 | case 1: 649 | red = green = blue = this.clampColorSample(pixelSamples[0], sampleProperties[0].bitsPerSample); 650 | break; 651 | 652 | // RGB Full Color 653 | case 2: 654 | red = this.clampColorSample(pixelSamples[0], sampleProperties[0].bitsPerSample); 655 | green = this.clampColorSample(pixelSamples[1], sampleProperties[1].bitsPerSample); 656 | blue = this.clampColorSample(pixelSamples[2], sampleProperties[2].bitsPerSample); 657 | break; 658 | 659 | // RGB Color Palette 660 | case 3: 661 | if (colorMapValues === undefined) { 662 | throw Error("Palette image missing color map"); 663 | } 664 | 665 | var colorMapIndex = pixelSamples[0]; 666 | 667 | red = this.clampColorSample(colorMapValues[colorMapIndex], 16); 668 | green = this.clampColorSample(colorMapValues[colorMapSampleSize + colorMapIndex], 16); 669 | blue = this.clampColorSample(colorMapValues[(2 * colorMapSampleSize) + colorMapIndex], 16); 670 | break; 671 | 672 | // Transparency mask 673 | case 4: 674 | throw RangeError( 'Not Yet Implemented: Transparency mask' ); 675 | break; 676 | 677 | // CMYK 678 | case 5: 679 | throw RangeError( 'Not Yet Implemented: CMYK' ); 680 | break; 681 | 682 | // YCbCr 683 | case 6: 684 | throw RangeError( 'Not Yet Implemented: YCbCr' ); 685 | break; 686 | 687 | // CIELab 688 | case 8: 689 | throw RangeError( 'Not Yet Implemented: CIELab' ); 690 | break; 691 | 692 | // Unknown Photometric Interpretation 693 | default: 694 | throw RangeError( 'Unknown Photometric Interpretation:', photometricInterpretation ); 695 | break; 696 | } 697 | 698 | ctx.fillStyle = this.makeRGBAFillValue(red, green, blue, opacity); 699 | ctx.fillRect(x, yPadding + y, 1, 1); 700 | } 701 | } 702 | 703 | numRowsInPreviousStrip = numRowsInStrip; 704 | } 705 | } 706 | 707 | /* for (var i = 0, numFileDirectories = this.fileDirectories.length; i < numFileDirectories; i++) { 708 | // Stuff 709 | }*/ 710 | 711 | return this.canvas; 712 | }, 713 | } 714 | -------------------------------------------------------------------------------- /wxs.three.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WXS.threejs 6 | 7 | 8 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------