├── 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 |
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 |
--------------------------------------------------------------------------------