UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

612 lines (523 loc) 21.8 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var THREE = _interopRequireWildcard(require("three")); var _GeometryLayer2 = _interopRequireDefault(require("./GeometryLayer")); var _InfoLayer = require("./InfoLayer"); var _Picking = _interopRequireDefault(require("../Core/Picking")); var _convertToTile = _interopRequireDefault(require("../Converter/convertToTile")); var _CancelledCommandException = _interopRequireDefault(require("../Core/Scheduler/CancelledCommandException")); var _ObjectRemovalHelper = _interopRequireDefault(require("../Process/ObjectRemovalHelper")); var _LayeredMaterialNodeProcessing = require("../Process/LayeredMaterialNodeProcessing"); var _Layer = require("./Layer"); var subdivisionVector = new THREE.Vector3(); var boundingSphereCenter = new THREE.Vector3(); /** * @property {InfoTiledGeometryLayer} info - Status information of layer * @property {boolean} isTiledGeometryLayer - Used to checkout whether this * layer is a TiledGeometryLayer. Default is true. You should not change this, * as it is used internally for optimisation. */ var TiledGeometryLayer = /*#__PURE__*/ function (_GeometryLayer) { (0, _inherits2["default"])(TiledGeometryLayer, _GeometryLayer); /** * A layer extending the {@link GeometryLayer}, but with a tiling notion. * * @constructor * @extends GeometryLayer * * @param {string} id - The id of the layer, that should be unique. It is * not mandatory, but an error will be emitted if this layer is added a * {@link View} that already has a layer going by that id. * @param {THREE.Object3d} object3d - The object3d used to contain the * geometry of the TiledGeometryLayer. It is usually a `THREE.Group`, but it * can be anything inheriting from a `THREE.Object3d`. * @param {Array} schemeTile - extents Array of root tiles * @param {Object} builder - builder geometry object * @param {Object} [config] - Optional configuration, all elements in it * will be merged as is in the layer. For example, if the configuration * contains three elements `name, protocol, extent`, these elements will be * available using `layer.name` or something else depending on the property * name. * @param {Source} [config.source] - Description and options of the source. * * @throws {Error} `object3d` must be a valid `THREE.Object3d`. */ function TiledGeometryLayer(id, object3d, schemeTile, builder, config) { var _this; (0, _classCallCheck2["default"])(this, TiledGeometryLayer); _this = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(TiledGeometryLayer).call(this, id, object3d, config)); _this.isTiledGeometryLayer = true; _this.protocol = 'tile'; _this.sseSubdivisionThreshold = _this.sseSubdivisionThreshold || 1.0; _this.schemeTile = schemeTile; _this.builder = builder; _this.info = new _InfoLayer.InfoTiledGeometryLayer((0, _assertThisInitialized2["default"])(_this)); if (!_this.schemeTile) { throw new Error("Cannot init tiled layer without schemeTile for layer ".concat(_this.id)); } if (!_this.builder) { throw new Error("Cannot init tiled layer without builder for layer ".concat(_this.id)); } _this.level0Nodes = []; var promises = []; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = _this.schemeTile[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var root = _step.value; promises.push(_this.convert(undefined, root)); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator["return"] != null) { _iterator["return"](); } } finally { if (_didIteratorError) { throw _iteratorError; } } } Promise.all(promises).then(function (level0s) { _this.level0Nodes = level0s; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = level0s[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var level0 = _step2.value; _this.object3d.add(level0); level0.updateMatrixWorld(); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) { _iterator2["return"](); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } }); return _this; } /** * Picking method for this layer. It uses the {@link Picking#pickTilesAt} * method. * * @param {View} view - The view instance. * @param {Object} coordinates - The coordinates to pick in the view. It * should have at least `x` and `y` properties. * @param {number} radius - Radius of the picking circle. * * @return {Array} An array containing all targets picked under the * specified coordinates. */ (0, _createClass2["default"])(TiledGeometryLayer, [{ key: "pickObjectsAt", value: function pickObjectsAt(view, coordinates) { var radius = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.options.defaultPickingRadius; return _Picking["default"].pickTilesAt(view, coordinates, radius, this); } /** * Does pre-update work on the context: * <ul> * <li>update the `colorLayers` and `elevationLayers`</li> * <li>update the `maxElevationLevel`</li> * </ul> * * Once this work is done, it returns a list of nodes to update. Depending * on the origin of `sources`, it can return a few things: * <ul> * <li>if `sources` is empty, it returns the first node of the layer * (stored as `level0Nodes`), which will trigger the update of the whole * tree</li> * <li>if the update is triggered by a camera move, the whole tree is * returned too</li> * <li>if `source.layer` is this layer, it means that `source` is a node; a * common ancestor will be found if there are multiple sources, with the * default common ancestor being the first source itself</li> * <li>else it returns the whole tree</li> * </ul> * * @param {Object} context - The context of the update; see the {@link * MainLoop} for more informations. * @param {Set<GeometryLayer|TileMesh>} sources - A list of sources to * generate a list of nodes to update. * * @return {TileMesh[]} The array of nodes to update. */ }, { key: "preUpdate", value: function preUpdate(context, sources) { var _this2 = this; if (sources.has(undefined) || sources.size == 0) { return this.level0Nodes; } context.colorLayers = context.view.getLayers(function (l, a) { return a && a.id == _this2.id && l.isColorLayer; }); context.elevationLayers = context.view.getLayers(function (l, a) { return a && a.id == _this2.id && l.isElevationLayer; }); context.maxElevationLevel = -1; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = context.elevationLayers[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var e = _step3.value; context.maxElevationLevel = Math.max(e.source.zoom.max, context.maxElevationLevel); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3["return"] != null) { _iterator3["return"](); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } if (context.maxElevationLevel == -1) { context.maxElevationLevel = Infinity; } // Prepare ColorLayer sequence order // In this moment, there is only one color layers sequence, because they are attached to tileLayer. // In future, the sequence must be returned by parent geometry layer. this.colorLayersOrder = _Layer.ImageryLayers.getColorLayersIdOrderedBySequence(context.colorLayers); var commonAncestor; var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = sources.values()[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var source = _step4.value; if (source.isCamera) { // if the change is caused by a camera move, no need to bother // to find common ancestor: we need to update the whole tree: // some invisible tiles may now be visible return this.level0Nodes; } if (source.layer === this) { if (!commonAncestor) { commonAncestor = source; } else { commonAncestor = source.findCommonAncestor(commonAncestor); if (!commonAncestor) { return this.level0Nodes; } } if (commonAncestor.material == null) { commonAncestor = undefined; } } } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4["return"] != null) { _iterator4["return"](); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } if (commonAncestor) { return [commonAncestor]; } else { return this.level0Nodes; } } /** * Update a node of this layer. The node will not be updated if: * <ul> * <li>it does not have a parent, then it is removed</li> * <li>its parent is being subdivided</li> * <li>is not visible in the camera</li> * </ul> * * @param {Object} context - The context of the update; see the {@link * MainLoop} for more informations. * @param {Layer} layer - Parameter to be removed once all update methods * have been aligned. * @param {TileMesh} node - The node to update. * * @returns {Object} */ }, { key: "update", value: function update(context, layer, node) { var _this3 = this; if (!node.parent) { return _ObjectRemovalHelper["default"].removeChildrenAndCleanup(this, node); } // early exit if parent' subdivision is in progress if (node.parent.pendingSubdivision) { node.visible = false; node.material.visible = false; this.info.update(node); return undefined; } // do proper culling node.visible = !this.culling(node, context.camera); if (node.visible) { var requestChildrenUpdate = false; node.material.visible = true; this.info.update(node); if (node.pendingSubdivision || TiledGeometryLayer.hasEnoughTexturesToSubdivide(context, node) && this.subdivision(context, this, node)) { this.subdivideNode(context, node); // display iff children aren't ready node.material.visible = node.pendingSubdivision; this.info.update(node); requestChildrenUpdate = true; } if (node.material.visible) { if (!requestChildrenUpdate) { return _ObjectRemovalHelper["default"].removeChildren(this, node); } } return requestChildrenUpdate ? node.children.filter(function (n) { return n.layer == _this3; }) : undefined; } node.material.visible = false; this.info.update(node); return _ObjectRemovalHelper["default"].removeChildren(this, node); } }, { key: "convert", value: function convert(requester, extent) { return _convertToTile["default"].convert(requester, extent, this); } }, { key: "countColorLayersTextures", value: function countColorLayersTextures() { return arguments.length; } // eslint-disable-next-line }, { key: "culling", value: function culling(node, camera) { return !camera.isBox3Visible(node.obb.box3D, node.obb.matrixWorld); } /** * Tell if a node has enough elevation or color textures to subdivide. * Subdivision is prevented if: * <ul> * <li>the node is covered by at least one elevation layer and if the node * doesn't have an elevation texture yet</li> * <li>a color texture is missing</li> * </ul> * * @param {Object} context - The context of the update; see the {@link * MainLoop} for more informations. * @param {TileMesh} node - The node to subdivide. * * @returns {boolean} False if the node can not be subdivided, true * otherwise. */ }, { key: "subdivideNode", /** * Subdivides a node of this layer. If the node is currently in the process * of subdivision, it will not do anything here. The subdivision of a node * will occur in four part, to create a quadtree. The extent of the node * will be divided in four parts: north-west, north-east, south-west and * south-east. Once all four nodes are created, they will be added to the * current node and the view of the context will be notified of this change. * * @param {Object} context - The context of the update; see the {@link * MainLoop} for more informations. * @param {TileMesh} node - The node to subdivide. * @return {Promise} { description_of_the_return_value } */ value: function subdivideNode(context, node) { var _this4 = this; if (!node.pendingSubdivision && !node.children.some(function (n) { return n.layer == _this4; })) { var extents = node.extent.subdivision(); // TODO: pendingSubdivision mechanism is fragile, get rid of it node.pendingSubdivision = true; var command = { /* mandatory */ view: context.view, requester: node, layer: this, priority: 10000, /* specific params */ extentsSource: extents, redraw: false }; return context.scheduler.execute(command).then(function (children) { var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = children[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var child = _step5.value; node.add(child); child.updateMatrixWorld(true); } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5["return"] != null) { _iterator5["return"](); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } node.pendingSubdivision = false; context.view.notifyChange(node, false); }, function (err) { node.pendingSubdivision = false; if (!(err instanceof _CancelledCommandException["default"])) { throw new Error(err); } }); } } /** * Test the subdvision of a node, compared to this layer. * * @param {Object} context - The context of the update; see the {@link * MainLoop} for more informations. * @param {PlanarLayer} layer - This layer, parameter to be removed. * @param {TileMesh} node - The node to test. * * @return {boolean} - True if the node is subdivisable, otherwise false. */ }, { key: "subdivision", value: function subdivision(context, layer, node) { if (node.level < this.minSubdivisionLevel) { return true; } if (this.maxSubdivisionLevel <= node.level) { return false; } // Prevent to subdivise the node if the current elevation level // we must avoid a tile, with level 20, inherits a level 3 elevation texture. // The induced geometric error is much too large and distorts the SSE var nodeLayer = node.material.getElevationLayer(); if (nodeLayer) { var currentTexture = nodeLayer.textures[0]; if (currentTexture && currentTexture.extent) { var offsetScale = nodeLayer.offsetScales[0]; var ratio = offsetScale.z; // ratio is node size / texture size if (ratio < 1 / Math.pow(2, this.maxDeltaElevationLevel)) { return false; } } } subdivisionVector.setFromMatrixScale(node.matrixWorld); boundingSphereCenter.copy(node.boundingSphere.center).applyMatrix4(node.matrixWorld); var distance = Math.max(0.0, context.camera.camera3D.position.distanceTo(boundingSphereCenter) - node.boundingSphere.radius * subdivisionVector.x); // Size projection on pixel of bounding node.screenSize = context.camera._preSSE * (2 * node.boundingSphere.radius * subdivisionVector.x) / distance; // The screen space error is calculated to have a correct texture display. // For the projection of a texture's texel to be less than or equal to one pixel var sse = node.screenSize / (_LayeredMaterialNodeProcessing.SIZE_DIAGONAL_TEXTURE * 2); return this.sseSubdivisionThreshold < sse; } }], [{ key: "hasEnoughTexturesToSubdivide", value: function hasEnoughTexturesToSubdivide(context, node) { var layerUpdateState = node.layerUpdateState || {}; var nodeLayer = node.material.getElevationLayer(); var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = context.elevationLayers[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var e = _step6.value; var extents = node.getExtentsByProjection(e.projection); if (!e.frozen && e.ready && e.source.extentsInsideLimit(extents) && (!nodeLayer || nodeLayer.level < 0)) { // no stop subdivision in the case of a loading error if (layerUpdateState[e.id] && layerUpdateState[e.id].inError()) { continue; } return false; } } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6["return"] != null) { _iterator6["return"](); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } var _iteratorNormalCompletion7 = true; var _didIteratorError7 = false; var _iteratorError7 = undefined; try { for (var _iterator7 = context.colorLayers[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { var c = _step7.value; if (c.frozen || !c.visible || !c.ready) { continue; } // no stop subdivision in the case of a loading error if (layerUpdateState[c.id] && layerUpdateState[c.id].inError()) { continue; } var _extents = node.getExtentsByProjection(c.projection); nodeLayer = node.material.getLayer(c.id); if (c.source.extentsInsideLimit(_extents) && (!nodeLayer || nodeLayer.level < 0)) { return false; } } } catch (err) { _didIteratorError7 = true; _iteratorError7 = err; } finally { try { if (!_iteratorNormalCompletion7 && _iterator7["return"] != null) { _iterator7["return"](); } } finally { if (_didIteratorError7) { throw _iteratorError7; } } } return true; } }]); return TiledGeometryLayer; }(_GeometryLayer2["default"]); var _default = TiledGeometryLayer; exports["default"] = _default;