UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

515 lines (420 loc) 16.1 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.$3dTilesCulling = $3dTilesCulling; exports.pre3dTilesUpdate = pre3dTilesUpdate; exports.computeNodeSSE = computeNodeSSE; exports.init3dTilesLayer = init3dTilesLayer; exports.process3dTilesNode = process3dTilesNode; exports.$3dTilesSubdivisionControl = $3dTilesSubdivisionControl; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); var THREE = _interopRequireWildcard(require("three")); var _Extent = _interopRequireDefault(require("../Core/Geographic/Extent")); function requestNewTile(view, scheduler, geometryLayer, metadata, parent, redraw) { var command = { /* mandatory */ view: view, requester: parent, layer: geometryLayer, priority: parent ? 1.0 / (parent.distance + 1) : 100, /* specific params */ metadata: metadata, redraw: redraw }; return scheduler.execute(command); } function getChildTiles(tile) { // only keep children that have the same layer and a valid tileId return tile.children.filter(function (n) { return n.layer == tile.layer && n.tileId; }); } function subdivideNode(context, layer, node, cullingTest) { if (node.additiveRefinement) { // Additive refinement can only fetch visible children. _subdivideNodeAdditive(context, layer, node, cullingTest); } else { // Substractive refinement on the other hand requires to replace // node with all of its children _subdivideNodeSubstractive(context, layer, node); } } var tmpBox3 = new THREE.Box3(); var tmpSphere = new THREE.Sphere(); function boundingVolumeToExtent(crs, volume, transform) { if (volume.region) { var box = tmpBox3.copy(volume.region.box3D).applyMatrix4(volume.region.matrixWorld); return _Extent["default"].fromBox3(crs, box); } else if (volume.box) { var _box = tmpBox3.copy(volume.box).applyMatrix4(transform); return _Extent["default"].fromBox3(crs, _box); } else { var sphere = tmpSphere.copy(volume.sphere).applyMatrix4(transform); return new _Extent["default"](crs, { west: sphere.center.x - sphere.radius, east: sphere.center.x + sphere.radius, south: sphere.center.y - sphere.radius, north: sphere.center.y + sphere.radius }); } } var tmpMatrix = new THREE.Matrix4(); function _subdivideNodeAdditive(context, layer, node, cullingTest) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { var _loop = function () { var child = _step.value; // child being downloaded => skip if (child.promise || child.loaded) { return "continue"; } // 'child' is only metadata (it's *not* a THREE.Object3D). 'cullingTest' needs // a matrixWorld, so we compute it: it's node's matrixWorld x child's transform var overrideMatrixWorld = node.matrixWorld; if (child.transform) { overrideMatrixWorld = tmpMatrix.multiplyMatrices(node.matrixWorld, child.transform); } var isVisible = cullingTest ? !cullingTest(context.camera, child, overrideMatrixWorld) : true; // child is not visible => skip if (!isVisible) { return "continue"; } child.promise = requestNewTile(context.view, context.scheduler, layer, child, node, true).then(function (tile) { node.add(tile); tile.updateMatrixWorld(); var extent = boundingVolumeToExtent(layer.extent.crs, tile.boundingVolume, tile.matrixWorld); tile.traverse(function (obj) { obj.extent = extent; }); context.view.notifyChange(child); child.loaded = true; delete child.promise; }); }; for (var _iterator = layer.tileIndex.index[node.tileId].children[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _ret = _loop(); if (_ret === "continue") continue; } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator["return"] != null) { _iterator["return"](); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } function _subdivideNodeSubstractive(context, layer, node) { if (!node.pendingSubdivision && getChildTiles(node).length == 0) { var _ret2 = function () { var childrenTiles = layer.tileIndex.index[node.tileId].children; if (childrenTiles === undefined || childrenTiles.length === 0) { return { v: void 0 }; } node.pendingSubdivision = true; var promises = []; var _loop2 = function (i) { promises.push(requestNewTile(context.view, context.scheduler, layer, childrenTiles[i], node, false).then(function (tile) { childrenTiles[i].loaded = true; node.add(tile); tile.updateMatrixWorld(); if (node.additiveRefinement) { context.view.notifyChange(node); } layer.tileIndex.index[tile.tileId].loaded = true; })); }; for (var i = 0; i < childrenTiles.length; i++) { _loop2(i); } Promise.all(promises).then(function () { node.pendingSubdivision = false; context.view.notifyChange(node); }); }(); if ((0, _typeof2["default"])(_ret2) === "object") return _ret2.v; } } function $3dTilesCulling(camera, node, tileMatrixWorld) { // For viewer Request Volume https://github.com/AnalyticalGraphicsInc/3d-tiles-samples/tree/master/tilesets/TilesetWithRequestVolume if (node.viewerRequestVolume) { var nodeViewer = node.viewerRequestVolume; if (nodeViewer.region) { // TODO return true; } if (nodeViewer.box) { // TODO return true; } if (nodeViewer.sphere) { var worldCoordinateCenter = nodeViewer.sphere.center.clone(); worldCoordinateCenter.applyMatrix4(tileMatrixWorld); // To check the distance between the center sphere and the camera if (!(camera.camera3D.position.distanceTo(worldCoordinateCenter) <= nodeViewer.sphere.radius)) { return true; } } } // For bounding volume if (node.boundingVolume) { var boundingVolume = node.boundingVolume; if (boundingVolume.region) { return !camera.isBox3Visible(boundingVolume.region.box3D, tileMatrixWorld.clone().multiply(boundingVolume.region.matrix)); } if (boundingVolume.box) { return !camera.isBox3Visible(boundingVolume.box, tileMatrixWorld); } if (boundingVolume.sphere) { return !camera.isSphereVisible(boundingVolume.sphere, tileMatrixWorld); } } return false; } // Cleanup all 3dtiles|three.js starting from a given node n. // n's children can be of 2 types: // - have a 'content' attribute -> it's a tileset and must // be cleaned with cleanup3dTileset() // - doesn't have 'content' -> it's a raw Object3D object, // and must be cleaned with _cleanupObject3D() function cleanup3dTileset(layer, n) { var depth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; // If this layer is not using additive refinement, we can only // clean a tile if all its neighbours are cleaned as well because // a tile can only be in 2 states: // - displayed and no children displayed // - hidden and all of its children displayed // So here we implement a conservative measure: if T is cleanable // we actually only clean its children tiles. var canCleanCompletely = n.additiveRefinement || depth > 0; for (var i = 0; i < n.children.length; i++) { // skip non-tiles elements if (!n.children[i].content) { if (canCleanCompletely) { n.children[i].traverse(_cleanupObject3D); } } else { cleanup3dTileset(layer, n.children[i], depth + 1); } } if (canCleanCompletely) { if (n.dispose) { n.dispose(); } delete n.content; layer.tileIndex.index[n.tileId].loaded = false; n.remove.apply(n, (0, _toConsumableArray2["default"])(n.children)); // and finally remove from parent if (depth == 0 && n.parent) { n.parent.remove(n); } } else { var tiles = getChildTiles(n); n.remove.apply(n, (0, _toConsumableArray2["default"])(tiles)); } } // This function is used to cleanup a Object3D hierarchy. // (no 3dtiles spectific code here because this is managed by cleanup3dTileset) function _cleanupObject3D(n) { // all children of 'n' are raw Object3D var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = n.children[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var child = _step2.value; _cleanupObject3D(child); } // free resources } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) { _iterator2["return"](); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } if (n.material) { // material can be either a THREE.Material object, or an array of // THREE.Material objects if (Array.isArray(n.material)) { var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = n.material[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var material = _step3.value; material.dispose(); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3["return"] != null) { _iterator3["return"](); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } else { n.material.dispose(); } } if (n.geometry) { n.geometry.dispose(); } n.remove.apply(n, (0, _toConsumableArray2["default"])(n.children)); } // this is a layer function pre3dTilesUpdate() { if (!this.visible) { return []; } // Elements removed are added in the layer._cleanableTiles list. // Since we simply push in this array, the first item is always // the oldest one. var now = Date.now(); if (this._cleanableTiles.length && now - this._cleanableTiles[0].cleanableSince > this.cleanupDelay) { // Make sure we don't clean root tile this.root.cleanableSince = undefined; var i = 0; for (; i < this._cleanableTiles.length; i++) { var elt = this._cleanableTiles[i]; if (now - elt.cleanableSince > this.cleanupDelay) { cleanup3dTileset(this, elt); } else { // later entries are younger break; } } // remove deleted elements from _cleanableTiles this._cleanableTiles.splice(0, i); } return [this.root]; } var boundingVolumeBox = new THREE.Box3(); var boundingVolumeSphere = new THREE.Sphere(); function computeNodeSSE(camera, node) { node.distance = 0; if (node.boundingVolume.region) { boundingVolumeBox.copy(node.boundingVolume.region.box3D); boundingVolumeBox.applyMatrix4(node.boundingVolume.region.matrixWorld); node.distance = boundingVolumeBox.distanceToPoint(camera.camera3D.position); } else if (node.boundingVolume.box) { // boundingVolume.box is affected by matrixWorld boundingVolumeBox.copy(node.boundingVolume.box); boundingVolumeBox.applyMatrix4(node.matrixWorld); node.distance = boundingVolumeBox.distanceToPoint(camera.camera3D.position); } else if (node.boundingVolume.sphere) { // boundingVolume.sphere is affected by matrixWorld boundingVolumeSphere.copy(node.boundingVolume.sphere); boundingVolumeSphere.applyMatrix4(node.matrixWorld); // TODO: see https://github.com/iTowns/itowns/issues/800 node.distance = Math.max(0.0, boundingVolumeSphere.distanceToPoint(camera.camera3D.position)); } else { return Infinity; } if (node.distance === 0) { // This test is needed in case geometricError = distance = 0 return Infinity; } return camera._preSSE * (node.geometricError / node.distance); } function init3dTilesLayer(view, scheduler, layer) { return requestNewTile(view, scheduler, layer, layer.tileset.root, undefined, true).then(function (tile) { delete layer.tileset; layer.object3d.add(tile); tile.updateMatrixWorld(); layer.tileIndex.index[tile.tileId].loaded = true; layer.root = tile; layer.extent = boundingVolumeToExtent(layer.projection || view.referenceCrs, tile.boundingVolume, tile.matrixWorld); }); } function setDisplayed(node, display) { // The geometry of the tile is not in node, but in node.content // To change the display state, we change node.content.visible instead of // node.material.visible if (node.content) { node.content.visible = display; } } function markForDeletion(layer, elt) { if (!elt.cleanableSince) { elt.cleanableSince = Date.now(); layer._cleanableTiles.push(elt); } } function process3dTilesNode() { var cullingTest = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : $3dTilesCulling; var subdivisionTest = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : $3dTilesSubdivisionControl; return function (context, layer, node) { // early exit if parent's subdivision is in progress if (node.parent.pendingSubdivision && !node.parent.additiveRefinement) { node.visible = false; return undefined; } // do proper culling var isVisible = cullingTest ? !cullingTest(context.camera, node, node.matrixWorld) : true; node.visible = isVisible; if (isVisible) { if (node.cleanableSince) { layer._cleanableTiles.splice(layer._cleanableTiles.indexOf(node), 1); node.cleanableSince = undefined; } var returnValue; if (node.pendingSubdivision || subdivisionTest(context, layer, node)) { subdivideNode(context, layer, node, cullingTest); // display iff children aren't ready setDisplayed(node, node.pendingSubdivision || node.additiveRefinement); returnValue = getChildTiles(node); } else { setDisplayed(node, true); var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = getChildTiles(node)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var n = _step4.value; n.visible = false; markForDeletion(layer, n); } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4["return"] != null) { _iterator4["return"](); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } } return returnValue; } markForDeletion(layer, node); }; } function $3dTilesSubdivisionControl(context, layer, node) { if (layer.tileIndex.index[node.tileId].children === undefined) { return false; } if (layer.tileIndex.index[node.tileId].isTileset) { return true; } var sse = computeNodeSSE(context.camera, node); return sse > layer.sseThreshold; }