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