UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

495 lines (416 loc) 17.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.$3dTilesIndex = $3dTilesIndex; exports.getObjectToUpdateForAttachedLayers = getObjectToUpdateForAttachedLayers; exports.configureTile = configureTile; exports["default"] = exports.$3dtilesLayer = exports.$3dTilesAbstractExtension = exports.$3dTilesExtensions = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var THREE = _interopRequireWildcard(require("three")); var _B3dmParser = _interopRequireDefault(require("../Parser/B3dmParser")); var _PntsParser = _interopRequireDefault(require("../Parser/PntsParser")); var _Fetcher = _interopRequireDefault(require("./Fetcher")); var _OBB = _interopRequireDefault(require("../Renderer/OBB")); var _Extent = _interopRequireDefault(require("../Core/Geographic/Extent")); var _dTilesProcessing = require("../Process/3dTilesProcessing"); var _Utf8Decoder = _interopRequireDefault(require("../Utils/Utf8Decoder")); /** @classdesc * Class mapping 3D Tiles extensions names to their associated parsing methods. */ var $3DTilesExtensions = /*#__PURE__*/ function () { function $3DTilesExtensions() { (0, _classCallCheck2["default"])(this, $3DTilesExtensions); this.extensionsMap = new Map(); } /** * Register a 3D Tiles extension: Maps an extension name to its parser * @param {String} extensionName - Name of the extension * @param {Function} parser - The function for parsing the extension */ (0, _createClass2["default"])($3DTilesExtensions, [{ key: "registerExtension", value: function registerExtension(extensionName, parser) { this.extensionsMap.set(extensionName, parser); } /** * Get the parser of a given extension * @param {String} extensionName - Name of the extension * @returns {Function} - The function for parsing the extension */ }, { key: "getParser", value: function getParser(extensionName) { return this.extensionsMap.get(extensionName); } /** * Test if an extension is registered * @param {String} extensionName - Name of the extension * @returns {boolean} - true if the extension is registered and false * otherwise */ }, { key: "isExtensionRegistered", value: function isExtensionRegistered(extensionName) { return this.extensionsMap.has(extensionName); } }]); return $3DTilesExtensions; }(); /** * Global object holding 3D Tiles extensions. Extensions must be registered * with their parser by the client. * @type {$3DTilesExtensions} */ var $3dTilesExtensions = new $3DTilesExtensions(); /** @classdesc * Abstract class for 3DTiles Extensions. Extensions implemented by the user * must inherit from this class. Example of extension extending this class: * [BatchTableHierarchyExtension]{@link BatchTableHierarchyExtension} */ exports.$3dTilesExtensions = $3dTilesExtensions; var $3dTilesAbstractExtension = /*#__PURE__*/ function () { function $3dTilesAbstractExtension() { (0, _classCallCheck2["default"])(this, $3dTilesAbstractExtension); if (this.constructor === $3dTilesAbstractExtension) { throw new TypeError('Abstract class "AbstractExtension" ' + 'cannot be instantiated directly'); } } /** * Method to get the displayable information related to a given feature * from an extension. All extensions must implement it (event if it * returns an empty object). * @param {integer} featureId - id of the feature */ // disable warning saying that we don't use 'this' in this method // eslint-disable-next-line (0, _createClass2["default"])($3dTilesAbstractExtension, [{ key: "getPickingInfo", value: function getPickingInfo() { throw new Error('You must implement getPickingInfo function ' + 'in your extension'); } }]); return $3dTilesAbstractExtension; }(); exports.$3dTilesAbstractExtension = $3dTilesAbstractExtension; function $3dTilesIndex(tileset, baseURL) { var counter = 1; this.index = {}; var inverseTileTransform = new THREE.Matrix4(); var recurse = function (node, baseURL, parent) { // compute transform (will become Object3D.matrix when the object is downloaded) node.transform = node.transform ? new THREE.Matrix4().fromArray(node.transform) : undefined; // The only reason to store _worldFromLocalTransform is because of extendTileset where we need the // transform chain for one node. node._worldFromLocalTransform = node.transform; if (parent && parent._worldFromLocalTransform) { if (node.transform) { node._worldFromLocalTransform = new THREE.Matrix4().multiplyMatrices(parent._worldFromLocalTransform, node.transform); } else { node._worldFromLocalTransform = parent._worldFromLocalTransform; } } // getBox only use inverseTileTransform for volume.region so let's not // compute the inverse matrix each time // Assumes that node.boundingVolume is defined if node.viewerRequestVolume is undefined if (node.viewerRequestVolume && node.viewerRequestVolume.region || node.boundingVolume.region) { if (node._worldFromLocalTransform) { inverseTileTransform.getInverse(node._worldFromLocalTransform); } else { inverseTileTransform.identity(); } } node.viewerRequestVolume = node.viewerRequestVolume ? getBox(node.viewerRequestVolume, inverseTileTransform) : undefined; node.boundingVolume = getBox(node.boundingVolume, inverseTileTransform); this.index[counter] = node; node.tileId = counter; node.baseURL = baseURL; counter++; if (node.children) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = node.children[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var child = _step.value; recurse(child, baseURL, node); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator["return"] != null) { _iterator["return"](); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } }.bind(this); recurse(tileset.root, baseURL); this.extendTileset = function (tileset, nodeId, baseURL) { recurse(tileset.root, baseURL, this.index[nodeId]); this.index[nodeId].children = [tileset.root]; this.index[nodeId].isTileset = true; }; } function getObjectToUpdateForAttachedLayers(meta) { if (meta.content) { var result = []; meta.content.traverse(function (obj) { if (obj.isObject3D && obj.material && obj.layer == meta.layer) { result.push(obj); } }); var p = meta.parent; if (p && p.content) { return { elements: result, parent: p.content }; } else { return { elements: result }; } } } /** @classdesc * Class for $3dtilesLayer. * * @example * var layer = new itowns.GeometryLayer('3dtiles-example'); * layer.protocol = '3d-tiles' * layer.url = 'http://example/tileset.json'; * * @property {string} url - tileset.json URL * @property {boolean|THREE.Material} [overrideMaterial=false] - override meshes material * embedded in the binary files and use a default one instead. * @property {THREE.Material} [material=THREE.PointsMaterial] - material cloned * and assigned each time a points mesh is created. * @property {number} [sseThreshold=16] - s(creen) s(pace) e(rror) threshold in pixels. * Define how many pixels the geometricError from a tile must cover to kick the * subdivision mechanism in. * @property {number} [cleanupDelay=1000] - delay in milliseconds after which an * undisplayed tiles will be removed from memory. * @property {Function} onTileContentLoaded - callback called when new content is added. */ var $3dtilesLayer = function $3dtilesLayer() { (0, _classCallCheck2["default"])(this, $3dtilesLayer); }; exports.$3dtilesLayer = $3dtilesLayer; function preprocessDataLayer(layer, view, scheduler) { layer.preUpdate = layer.preUpdate || _dTilesProcessing.pre3dTilesUpdate; layer.update = layer.update || (0, _dTilesProcessing.process3dTilesNode)(); layer.sseThreshold = layer.sseThreshold || 16; layer.cleanupDelay = layer.cleanupDelay || 1000; layer.onTileContentLoaded = layer.onTileContentLoaded || function () {}; // override the default method, since updated objects are metadata in this case layer.getObjectToUpdateForAttachedLayers = getObjectToUpdateForAttachedLayers; layer._cleanableTiles = []; return _Fetcher["default"].json(layer.url, layer.networkOptions).then(function (tileset) { layer.tileset = tileset; // Verify that extensions of the tileset have been registered to // $3dTilesExtensions if (tileset.extensionsUsed) { var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = tileset.extensionsUsed[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var extensionUsed = _step2.value; // if current extension is not registered if (!$3dTilesExtensions.isExtensionRegistered(extensionUsed)) { if (tileset.extensionsRequired && tileset.extensionsRequired.includes(extensionUsed)) { console.error("3D Tiles tileset required extension \"".concat(extensionUsed, "\" must be registered to $3dTilesExtensions global object of iTowns to be parsed and used.")); } else { console.warn("3D Tiles tileset used extension \"".concat(extensionUsed, "\" must be registered to $3dTilesExtensions global object of iTowns to be parsed and used.")); } } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) { _iterator2["return"](); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } } var urlPrefix = layer.url.slice(0, layer.url.lastIndexOf('/') + 1); layer.tileIndex = new $3dTilesIndex(tileset, urlPrefix); layer.asset = tileset.asset; return (0, _dTilesProcessing.init3dTilesLayer)(view, scheduler, layer, tileset.root); }); } var extent = new _Extent["default"]('EPSG:4326', 0, 0, 0, 0); function getBox(volume, inverseTileTransform) { if (volume.region) { var region = volume.region; extent.set(THREE.Math.radToDeg(region[0]), THREE.Math.radToDeg(region[2]), THREE.Math.radToDeg(region[1]), THREE.Math.radToDeg(region[3])); var box = new _OBB["default"]().setFromExtent(extent); // at this point box.matrix = box.epsg4978_from_local, so // we transform it in parent_from_local by using parent's epsg4978_from_local // which from our point of view is epsg4978_from_parent. // box.matrix = (epsg4978_from_parent ^ -1) * epsg4978_from_local // = parent_from_epsg4978 * epsg4978_from_local // = parent_from_local box.matrix.premultiply(inverseTileTransform); // update position, rotation and scale box.matrix.decompose(box.position, box.quaternion, box.scale); return { region: box }; } else if (volume.box) { // TODO: only works for axis aligned boxes var _box = volume.box; // box[0], box[1], box[2] = center of the box // box[3], box[4], box[5] = x axis direction and half-length // box[6], box[7], box[8] = y axis direction and half-length // box[9], box[10], box[11] = z axis direction and half-length var center = new THREE.Vector3(_box[0], _box[1], _box[2]); var w = center.x - _box[3]; var e = center.x + _box[3]; var s = center.y - _box[7]; var n = center.y + _box[7]; var b = center.z - _box[11]; var t = center.z + _box[11]; return { box: new THREE.Box3(new THREE.Vector3(w, s, b), new THREE.Vector3(e, n, t)) }; } else if (volume.sphere) { var sphere = new THREE.Sphere(new THREE.Vector3(volume.sphere[0], volume.sphere[1], volume.sphere[2]), volume.sphere[3]); return { sphere: sphere }; } } function b3dmToMesh(data, layer, url) { var urlBase = THREE.LoaderUtils.extractUrlBase(url); var options = { gltfUpAxis: layer.asset.gltfUpAxis, urlBase: urlBase, overrideMaterials: layer.overrideMaterials, doNotPatchMaterial: layer.doNotPatchMaterial, opacity: layer.opacity }; return _B3dmParser["default"].parse(data, options).then(function (result) { var batchTable = result.batchTable; var object3d = result.gltf.scene; return { batchTable: batchTable, object3d: object3d }; }); } function pntsParse(data, layer) { return _PntsParser["default"].parse(data).then(function (result) { var material = layer.material ? layer.material.clone() : new THREE.PointsMaterial({ size: 0.05, vertexColors: THREE.VertexColors }); // creation points with geometry and material var points = new THREE.Points(result.point.geometry, material); if (result.point.offset) { points.position.copy(result.point.offset); } return { object3d: points }; }); } function configureTile(tile, layer, metadata, parent) { tile.frustumCulled = false; tile.layer = layer; // parse metadata if (metadata.transform) { tile.applyMatrix(metadata.transform); } tile.geometricError = metadata.geometricError; tile.tileId = metadata.tileId; if (metadata.refine) { tile.additiveRefinement = metadata.refine.toUpperCase() === 'ADD'; } else { tile.additiveRefinement = parent ? parent.additiveRefinement : false; } tile.viewerRequestVolume = metadata.viewerRequestVolume; tile.boundingVolume = metadata.boundingVolume; if (tile.boundingVolume.region) { tile.add(tile.boundingVolume.region); } tile.updateMatrixWorld(); } function executeCommand(command) { var layer = command.layer; var metadata = command.metadata; var tile = new THREE.Object3D(); configureTile(tile, layer, metadata, command.requester); // Patch for supporting 3D Tiles pre 1.0 (metadata.content.url) and 1.0 // (metadata.content.uri) var path = metadata.content && (metadata.content.url || metadata.content.uri); var setLayer = function (obj) { obj.layers.set(layer.threejsLayer); obj.userData.metadata = metadata; obj.layer = layer; if (obj.material) { obj.material.transparent = layer.opacity < 1.0; obj.material.opacity = layer.opacity; obj.material.wireframe = layer.wireframe; } }; if (path) { // Check if we have relative or absolute url (with tileset's lopocs for example) var url = path.startsWith('http') ? path : metadata.baseURL + path; var supportedFormats = { b3dm: b3dmToMesh, pnts: pntsParse }; return _Fetcher["default"].arrayBuffer(url, layer.networkOptions).then(function (result) { if (result !== undefined) { var func; var magic = _Utf8Decoder["default"].decode(new Uint8Array(result, 0, 4)); if (magic[0] === '{') { result = JSON.parse(_Utf8Decoder["default"].decode(new Uint8Array(result))); var newPrefix = url.slice(0, url.lastIndexOf('/') + 1); layer.tileIndex.extendTileset(result, metadata.tileId, newPrefix); } else if (magic == 'b3dm') { func = supportedFormats.b3dm; } else if (magic == 'pnts') { func = supportedFormats.pnts; } else { return Promise.reject("Unsupported magic code ".concat(magic)); } if (func) { // TODO: request should be delayed if there is a viewerRequestVolume return func(result, layer, url).then(function (content) { layer.onTileContentLoaded(content); tile.content = content.object3d; if (content.batchTable) { tile.batchTable = content.batchTable; } tile.add(content.object3d); tile.traverse(setLayer); return tile; }); } } tile.traverse(setLayer); return tile; }); } else { tile.traverse(setLayer); return Promise.resolve(tile); } } var _default = { preprocessDataLayer: preprocessDataLayer, executeCommand: executeCommand }; exports["default"] = _default;