UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

417 lines (339 loc) 15.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.readTextureValueWithBilinearFiltering = readTextureValueWithBilinearFiltering; exports["default"] = void 0; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var THREE = _interopRequireWildcard(require("three")); var _Coordinates = _interopRequireDefault(require("../Core/Geographic/Coordinates")); var _placeObjectOnGround = _interopRequireDefault(require("./placeObjectOnGround")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } var FAST_READ_Z = 0; var PRECISE_READ_Z = 1; /** * Utility module to retrieve elevation at a given coordinates. The returned * value is read in the elevation textures used by the graphics card to render * the tiles (globe or plane). This implies that the return value may change * depending on the current tile resolution. * * @module DEMUtils */ var _default = { /** * Gives the elevation value of a {@link TiledGeometryLayer}, at a specific * {@link Coordinates}. * * @param {TiledGeometryLayer} layer - The tile layer owning the elevation * textures we're going to query. This is typically a `GlobeLayer` or * `PlanarLayer` (accessible through `view.tileLayer`). * @param {Coordinates} coord - The coordinates that we're interested in. * @param {number} [method=FAST_READ_Z] - There are two available methods: * `FAST_READ_Z` (default) or `PRECISE_READ_Z`. The first one is faster, * while the second one is slower but gives better precision. * @param {TileMesh[]} [tileHint] - Optional array of tiles to speed up the * process. You can give candidates tiles likely to contain `coord`. * Otherwise the lookup process starts from the root of `layer`. * * @return {number} If found, a value in meters is returned; otherwise * `undefined`. */ getElevationValueAt: function getElevationValueAt(layer, coord) { var method = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : FAST_READ_Z; var tileHint = arguments.length > 3 ? arguments[3] : undefined; var result = _readZ(layer, method, coord, tileHint || layer.level0Nodes); if (result) { return result.coord.z; } }, /** * @typedef Terrain * @type {Object} * * @property {Coordinates} coord - Pick coordinate with the elevation in coord.z. * @property {THREE.Texture} texture - the picked elevation texture. * The texture where the `z` value has been read from * @property {TileMesh} tile - the picked tile and the tile containing the texture */ /** * Gives a {@link Terrain} object, at a specific {@link Coordinates}. The returned * object is as follow: * - `coord`, Coordinate, coord.z is the value in meters of the elevation at the coordinates * - `texture`, the texture where the `z` value has been read from * - `tile`, the tile containing the texture * @example * // place mesh on the ground * const coord = new Coordinates('EPSG:4326', 6, 45); * const result = DEMUtils.getTerrainObjectAt(view.tileLayer, coord) * mesh.position.copy(result.coord.as(view.referenceCrs)); * view.scene.add(mesh); * mesh.updateMatrixWorld(); * * * @param {TiledGeometryLayer} layer - The tile layer owning the elevation * textures we're going to query. This is typically a `GlobeLayer` or * `PlanarLayer` (accessible through `view.tileLayer`). * @param {Coordinates} coord - The coordinates that we're interested in. * @param {number} [method=FAST_READ_Z] - There are two available methods: * `FAST_READ_Z` (default) or `PRECISE_READ_Z`. The first one is faster, * while the second one is slower but gives better precision. * @param {TileMesh[]} [tileHint] - Optional array of tiles to speed up the * process. You can give candidates tiles likely to contain `coord`. * Otherwise the lookup process starts from the root of `layer`. * @param {Object} [cache] - Object to cache previous result and speed up the next `getTerrainObjectAt`` use. * * @return {Terrain} - The {@link Terrain} object. */ getTerrainObjectAt: function getTerrainObjectAt(layer, coord) { var method = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : FAST_READ_Z; var tileHint = arguments.length > 3 ? arguments[3] : undefined; var cache = arguments.length > 4 ? arguments[4] : undefined; return _readZ(layer, method, coord, tileHint || layer.level0Nodes, cache); }, FAST_READ_Z: FAST_READ_Z, PRECISE_READ_Z: PRECISE_READ_Z, placeObjectOnGround: _placeObjectOnGround["default"] }; exports["default"] = _default; function tileAt(pt, tile) { if (tile.extent) { if (!tile.extent.isPointInside(pt)) { return undefined; } for (var i = 0; i < tile.children.length; i++) { var t = tileAt(pt, tile.children[i]); if (t) { return t; } } var tileLayer = tile.material.getElevationLayer(); if (tileLayer && tileLayer.level >= 0) { return tile; } return undefined; } } var _canvas; function _readTextureValueAt(metadata, texture) { for (var _len = arguments.length, uv = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { uv[_key - 2] = arguments[_key]; } for (var i = 0; i < uv.length; i += 2) { uv[i] = THREE.MathUtils.clamp(uv[i], 0, texture.image.width - 1); uv[i + 1] = THREE.MathUtils.clamp(uv[i + 1], 0, texture.image.height - 1); } if (texture.image.data) { // read a single value if (uv.length === 2) { var v = texture.image.data[uv[1] * texture.image.width + uv[0]]; return v != metadata.noDataValue ? v : undefined; } // or read multiple values var result = []; for (var _i = 0; _i < uv.length; _i += 2) { var _v = texture.image.data[uv[_i + 1] * texture.image.width + uv[_i]]; result.push(_v != metadata.noDataValue ? _v : undefined); } return result; } else { if (!_canvas) { _canvas = document.createElement('canvas'); _canvas.width = 2; _canvas.height = 2; } var minx = Infinity; var miny = Infinity; var maxx = -Infinity; var maxy = -Infinity; for (var _i2 = 0; _i2 < uv.length; _i2 += 2) { minx = Math.min(uv[_i2], minx); miny = Math.min(uv[_i2 + 1], miny); maxx = Math.max(uv[_i2], maxx); maxy = Math.max(uv[_i2 + 1], maxy); } var dw = maxx - minx + 1; var dh = maxy - miny + 1; _canvas.width = Math.max(_canvas.width, dw); _canvas.height = Math.max(_canvas.height, dh); var ctx = _canvas.getContext('2d'); ctx.drawImage(texture.image, minx, miny, dw, dh, 0, 0, dw, dh); var d = ctx.getImageData(0, 0, dw, dh); var _result = []; for (var _i3 = 0; _i3 < uv.length; _i3 += 2) { var ox = uv[_i3] - minx; var oy = uv[_i3 + 1] - miny; // d is 4 bytes per pixel var _v2 = THREE.MathUtils.lerp(metadata.colorTextureElevationMinZ, metadata.colorTextureElevationMaxZ, d.data[4 * oy * dw + 4 * ox] / 255); _result.push(_v2 != metadata.noDataValue ? _v2 : undefined); } if (uv.length === 2) { return _result[0]; } else { return _result; } } } function _convertUVtoTextureCoords(texture, u, v) { var width = texture.image.width; var height = texture.image.height; var up = Math.max(0, u * width - 0.5); var vp = Math.max(0, v * height - 0.5); var u1 = Math.floor(up); var u2 = Math.ceil(up); var v1 = Math.floor(vp); var v2 = Math.ceil(vp); return { u1: u1, u2: u2, v1: v1, v2: v2, wu: up - u1, wv: vp - v1 }; } function _readTextureValueNearestFiltering(metadata, texture, vertexU, vertexV) { var coords = _convertUVtoTextureCoords(texture, vertexU, vertexV); var u = coords.wu <= 0 ? coords.u1 : coords.u2; var v = coords.wv <= 0 ? coords.v1 : coords.v2; return _readTextureValueAt(metadata, texture, u, v); } function _lerpWithUndefinedCheck(x, y, t) { if (x == undefined) { return y; } else if (y == undefined) { return x; } else { return THREE.MathUtils.lerp(x, y, t); } } function readTextureValueWithBilinearFiltering(metadata, texture, vertexU, vertexV) { var coords = _convertUVtoTextureCoords(texture, vertexU, vertexV); var _readTextureValueAt2 = _readTextureValueAt(metadata, texture, coords.u1, coords.v1, coords.u2, coords.v1, coords.u1, coords.v2, coords.u2, coords.v2), _readTextureValueAt3 = (0, _slicedToArray2["default"])(_readTextureValueAt2, 4), z11 = _readTextureValueAt3[0], z21 = _readTextureValueAt3[1], z12 = _readTextureValueAt3[2], z22 = _readTextureValueAt3[3]; // horizontal filtering var zu1 = _lerpWithUndefinedCheck(z11, z21, coords.wu); var zu2 = _lerpWithUndefinedCheck(z12, z22, coords.wu); // then vertical filtering return _lerpWithUndefinedCheck(zu1, zu2, coords.wv); } function _readZFast(layer, texture, uv) { var elevationLayer = layer.attachedLayers.filter(function (l) { return l.isElevationLayer; })[0]; return _readTextureValueNearestFiltering(elevationLayer, texture, uv.x, uv.y); } var bary = new THREE.Vector3(); function _readZCorrect(layer, texture, uv, tileDimensions, tileOwnerDimensions) { // We need to emulate the vertex shader code that does 2 thing: // - interpolate (u, v) between triangle vertices: u,v will be multiple of 1/nsegments // (for now assume nsegments == 16) // - read elevation texture at (u, v) for // Determine u,v based on the vertices count. // 'modulo' is the gap (in [0, 1]) between 2 successive vertices in the geometry // e.g if you have 5 vertices, the only possible values for u (or v) are: 0, 0.25, 0.5, 0.75, 1 // so modulo would be 0.25 // note: currently the number of segments is hard-coded to 16 (see TileProvider) => 17 vertices var modulo = tileDimensions.x / tileOwnerDimensions.x / (17 - 1); var u = Math.floor(uv.x / modulo) * modulo; var v = Math.floor(uv.y / modulo) * modulo; if (u == 1) { u -= modulo; } if (v == 1) { v -= modulo; } // Build 4 vertices, 3 of them will be our triangle: // 11---21 // | / | // | / | // | / | // 21---22 var u1 = u; var u2 = u + modulo; var v1 = v; var v2 = v + modulo; // Our multiple z-value will be weigh-blended, depending on the distance of the real point // so lu (resp. lv) are the weight. When lu -> 0 (resp. 1) the final value -> z at u1 (resp. u2) var lu = (uv.x - u) / modulo; var lv = (uv.y - v) / modulo; // Determine if we're going to read the vertices from the top-left or lower-right triangle // (low-right = on the line 21-22 or under the diagonal lu = 1 - lv) var tri = new THREE.Triangle(new THREE.Vector3(u1, v2), new THREE.Vector3(u2, v1), lv == 1 || lu / (1 - lv) >= 1 ? new THREE.Vector3(u2, v2) : new THREE.Vector3(u1, v1)); // bary holds the respective weight of each vertices of the triangles tri.getBarycoord(new THREE.Vector3(uv.x, uv.y), bary); var elevationLayer = layer.attachedLayers.filter(function (l) { return l.isElevationLayer; })[0]; // read the 3 interesting values var z1 = readTextureValueWithBilinearFiltering(elevationLayer, texture, tri.a.x, tri.a.y); var z2 = readTextureValueWithBilinearFiltering(elevationLayer, texture, tri.b.x, tri.b.y); var z3 = readTextureValueWithBilinearFiltering(elevationLayer, texture, tri.c.x, tri.c.y); // Blend with bary return z1 * bary.x + z2 * bary.y + z3 * bary.z; } var temp = { v: new THREE.Vector3(), coord1: new _Coordinates["default"]('EPSG:4978'), coord2: new _Coordinates["default"]('EPSG:4978'), offset: new THREE.Vector2() }; var dimension = new THREE.Vector2(); function offsetInExtent(point, extent) { var target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new THREE.Vector2(); if (point.crs != extent.crs) { throw new Error("Unsupported mix: ".concat(point.crs, " and ").concat(extent.crs)); } extent.dimensions(dimension); var originX = (point.x - extent.west) / dimension.x; var originY = (extent.north - point.y) / dimension.y; return target.set(originX, originY); } function _readZ(layer, method, coord, nodes, cache) { var pt = coord.as(layer.extent.crs, temp.coord1); var tileWithValidElevationTexture = null; // first check in cache if (cache && cache.tile && cache.tile.material) { tileWithValidElevationTexture = tileAt(pt, cache.tile); } for (var i = 0; !tileWithValidElevationTexture && i < nodes.length; i++) { tileWithValidElevationTexture = tileAt(pt, nodes[i]); } if (!tileWithValidElevationTexture) { // failed to find a tile, abort return; } var tile = tileWithValidElevationTexture; var tileLayer = tile.material.getElevationLayer(); var src = tileLayer.textures[0]; // check cache value if existing if (cache) { if (cache.id === src.id && cache.version === src.version) { return { coord: pt, texture: src, tile: tile }; } } // Assuming that tiles are split in 4 children, we lookup the parent that // really owns this texture var stepsUpInHierarchy = Math.round(Math.log2(1.0 / tileLayer.offsetScales[0].z)); for (var _i4 = 0; _i4 < stepsUpInHierarchy; _i4++) { tileWithValidElevationTexture = tileWithValidElevationTexture.parent; } // offset = offset from top-left offsetInExtent(pt, tileWithValidElevationTexture.extent, temp.offset); // At this point we have: // - tileWithValidElevationTexture.texture.image which is the current image // used for rendering // - offset which is the offset in this texture for the coordinate we're // interested in // We now have 2 options: // - the fast one: read the value of tileWithValidElevationTexture.texture.image // at (offset.x, offset.y) and we're done // - the correct one: emulate the vertex shader code if (method == PRECISE_READ_Z) { pt.z = _readZCorrect(layer, src, temp.offset, tile.extent.dimensions(), tileWithValidElevationTexture.extent.dimensions()); } else { pt.z = _readZFast(layer, src, temp.offset); } if (pt.z != undefined) { return { coord: pt, texture: src, tile: tile }; } }