itowns
Version:
A JS/WebGL framework for 3D geospatial data visualization
612 lines (523 loc) • 21.8 kB
JavaScript
"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;