123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- import Cartographic from "../Core/Cartographic.js";
- import defaultValue from "../Core/defaultValue.js";
- import defined from "../Core/defined.js";
- import Ellipsoid from "../Core/Ellipsoid.js";
- import HeadingPitchRoll from "../Core/HeadingPitchRoll.js";
- import CesiumMath from "../Core/Math.js";
- import Matrix3 from "../Core/Matrix3.js";
- import Matrix4 from "../Core/Matrix4.js";
- import Resource from "../Core/Resource.js";
- import Quaternion from "../Core/Quaternion.js";
- import Transforms from "../Core/Transforms.js";
- import Cesium3DTile from "./Cesium3DTile.js";
- import I3SDataProvider from "./I3SDataProvider.js";
- import I3SFeature from "./I3SFeature.js";
- import I3SField from "./I3SField.js";
- import I3SGeometry from "./I3SGeometry.js";
- /**
- * This class implements an I3S Node. In CesiumJS each I3SNode creates a Cesium3DTile.
- * <p>
- * Do not construct this directly, instead access tiles through {@link I3SLayer}.
- * </p>
- * @alias I3SNode
- * @internalConstructor
- */
- function I3SNode(parent, ref, isRoot) {
- let level;
- let layer;
- let nodeIndex;
- let resource;
- if (isRoot) {
- level = 0;
- layer = parent;
- } else {
- level = parent._level + 1;
- layer = parent._layer;
- }
- if (typeof ref === "number") {
- nodeIndex = ref;
- } else {
- resource = parent.resource.getDerivedResource({
- url: `${ref}/`,
- });
- }
- this._parent = parent;
- this._dataProvider = parent._dataProvider;
- this._isRoot = isRoot;
- this._level = level;
- this._layer = layer;
- this._nodeIndex = nodeIndex;
- this._resource = resource;
- this._isLoading = false;
- this._tile = undefined;
- this._data = undefined;
- this._geometryData = [];
- this._featureData = [];
- this._fields = {};
- this._children = [];
- this._childrenReadyPromise = undefined;
- this._globalTransform = undefined;
- this._inverseGlobalTransform = undefined;
- this._inverseRotationMatrix = undefined;
- }
- Object.defineProperties(I3SNode.prototype, {
- /**
- * Gets the resource for the node.
- * @memberof I3SNode.prototype
- * @type {Resource}
- * @readonly
- */
- resource: {
- get: function () {
- return this._resource;
- },
- },
- /**
- * Gets the parent layer.
- * @memberof I3SNode.prototype
- * @type {I3SLayer}
- * @readonly
- */
- layer: {
- get: function () {
- return this._layer;
- },
- },
- /**
- * Gets the parent node.
- * @memberof I3SNode.prototype
- * @type {I3SNode|undefined}
- * @readonly
- */
- parent: {
- get: function () {
- return this._parent;
- },
- },
- /**
- * Gets the children nodes.
- * @memberof I3SNode.prototype
- * @type {I3SNode[]}
- * @readonly
- */
- children: {
- get: function () {
- return this._children;
- },
- },
- /**
- * Gets the collection of geometries.
- * @memberof I3SNode.prototype
- * @type {I3SGeometry[]}
- * @readonly
- */
- geometryData: {
- get: function () {
- return this._geometryData;
- },
- },
- /**
- * Gets the collection of features.
- * @memberof I3SNode.prototype
- * @type {I3SFeature[]}
- * @readonly
- */
- featureData: {
- get: function () {
- return this._featureData;
- },
- },
- /**
- * Gets the collection of fields.
- * @memberof I3SNode.prototype
- * @type {I3SField[]}
- * @readonly
- */
- fields: {
- get: function () {
- return this._fields;
- },
- },
- /**
- * Gets the Cesium3DTile for this node.
- * @memberof I3SNode.prototype
- * @type {Cesium3DTile}
- * @readonly
- */
- tile: {
- get: function () {
- return this._tile;
- },
- },
- /**
- * Gets the I3S data for this object.
- * @memberof I3SNode.prototype
- * @type {object}
- * @readonly
- */
- data: {
- get: function () {
- return this._data;
- },
- },
- });
- /**
- * @private
- */
- I3SNode.prototype.load = async function () {
- const that = this;
- function processData() {
- if (!that._isRoot) {
- // Create a new tile
- const tileDefinition = that._create3DTileDefinition();
- that._tile = new Cesium3DTile(
- that._layer._tileset,
- that._dataProvider.resource,
- tileDefinition,
- that._parent._tile
- );
- that._tile._i3sNode = that;
- }
- }
- // If we don't have a nodepage index load from json
- if (!defined(this._nodeIndex)) {
- const data = await I3SDataProvider.loadJson(
- this._resource,
- this._dataProvider._traceFetches
- );
- that._data = data;
- processData();
- return;
- }
- const node = await this._layer._getNodeInNodePages(this._nodeIndex);
- that._data = node;
- let uri;
- if (that._isRoot) {
- uri = "nodes/root/";
- } else if (defined(node.mesh)) {
- const uriIndex = node.mesh.geometry.resource;
- uri = `../${uriIndex}/`;
- }
- if (defined(uri)) {
- that._resource = that._parent.resource.getDerivedResource({ url: uri });
- }
- processData();
- };
- /**
- * Loads the node fields.
- * @returns {Promise<void>} A promise that is resolved when the I3S Node fields are loaded
- */
- I3SNode.prototype.loadFields = function () {
- // Check if we must load fields
- const fields = this._layer._data.attributeStorageInfo;
- const that = this;
- function createAndLoadField(fields, index) {
- const newField = new I3SField(that, fields[index]);
- that._fields[newField._storageInfo.name] = newField;
- return newField.load();
- }
- const promises = [];
- if (defined(fields)) {
- for (let i = 0; i < fields.length; i++) {
- promises.push(createAndLoadField(fields, i));
- }
- }
- return Promise.all(promises);
- };
- /**
- * Returns the fields for a given picked position
- * @param {Cartesian3} pickedPosition The picked position
- * @returns {object} Object containing field names and their values
- */
- I3SNode.prototype.getFieldsForPickedPosition = function (pickedPosition) {
- const geometry = this.geometryData[0];
- if (!defined(geometry.customAttributes.featureIndex)) {
- return {};
- }
- const location = geometry.getClosestPointIndexOnTriangle(
- pickedPosition.x,
- pickedPosition.y,
- pickedPosition.z
- );
- if (
- location.index === -1 ||
- location.index > geometry.customAttributes.featureIndex.length
- ) {
- return {};
- }
- const featureIndex = geometry.customAttributes.featureIndex[location.index];
- return this.getFieldsForFeature(featureIndex);
- };
- /**
- * Returns the fields for a given feature
- * @param {number} featureIndex Index of the feature whose attributes we want to get
- * @returns {object} Object containing field names and their values
- */
- I3SNode.prototype.getFieldsForFeature = function (featureIndex) {
- const featureFields = {};
- for (const fieldName in this.fields) {
- if (this.fields.hasOwnProperty(fieldName)) {
- const field = this.fields[fieldName];
- if (featureIndex >= 0 && featureIndex < field.values.length) {
- featureFields[field.name] = field.values[featureIndex];
- }
- }
- }
- return featureFields;
- };
- /**
- * @private
- */
- I3SNode.prototype._loadChildren = function () {
- const that = this;
- // If the promise for loading the children was already created, just return it
- if (defined(this._childrenReadyPromise)) {
- return this._childrenReadyPromise;
- }
- const childPromises = [];
- if (defined(that._data.children)) {
- for (
- let childIndex = 0;
- childIndex < that._data.children.length;
- childIndex++
- ) {
- const child = that._data.children[childIndex];
- const newChild = new I3SNode(
- that,
- defaultValue(child.href, child),
- false
- );
- that._children.push(newChild);
- childPromises.push(newChild.load());
- }
- }
- this._childrenReadyPromise = Promise.all(childPromises).then(function () {
- for (let i = 0; i < that._children.length; i++) {
- that._tile.children.push(that._children[i]._tile);
- }
- });
- return this._childrenReadyPromise;
- };
- /**
- * @private
- */
- I3SNode.prototype._loadGeometryData = function () {
- const geometryPromises = [];
- // To debug decoding for a specific tile, add a condition
- // that wraps this if/else to match the tile uri
- if (defined(this._data.geometryData)) {
- for (
- let geomIndex = 0;
- geomIndex < this._data.geometryData.length;
- geomIndex++
- ) {
- const curGeometryData = new I3SGeometry(
- this,
- this._data.geometryData[geomIndex].href
- );
- this._geometryData.push(curGeometryData);
- geometryPromises.push(curGeometryData.load());
- }
- } else if (defined(this._data.mesh)) {
- const geometryDefinition = this._layer._findBestGeometryBuffers(
- this._data.mesh.geometry.definition,
- ["position", "uv0"]
- );
- const geometryURI = `./geometries/${geometryDefinition.bufferIndex}/`;
- const newGeometryData = new I3SGeometry(this, geometryURI);
- newGeometryData._geometryDefinitions = geometryDefinition.definition;
- newGeometryData._geometryBufferInfo = geometryDefinition.geometryBufferInfo;
- this._geometryData.push(newGeometryData);
- geometryPromises.push(newGeometryData.load());
- }
- return Promise.all(geometryPromises);
- };
- /**
- * @private
- */
- I3SNode.prototype._loadFeatureData = function () {
- const featurePromises = [];
- // To debug decoding for a specific tile, add a condition
- // that wraps this if/else to match the tile uri
- if (defined(this._data.featureData)) {
- for (
- let featureIndex = 0;
- featureIndex < this._data.featureData.length;
- featureIndex++
- ) {
- const newFeatureData = new I3SFeature(
- this,
- this._data.featureData[featureIndex].href
- );
- this._featureData.push(newFeatureData);
- featurePromises.push(newFeatureData.load());
- }
- }
- return Promise.all(featurePromises);
- };
- /**
- * @private
- */
- I3SNode.prototype._clearGeometryData = function () {
- this._geometryData = [];
- };
- /**
- * @private
- */
- I3SNode.prototype._create3DTileDefinition = function () {
- const obb = this._data.obb;
- const mbs = this._data.mbs;
- if (!defined(obb) && !defined(mbs)) {
- console.error("Failed to load I3S node. Bounding volume is required.");
- return undefined;
- }
- let geoPosition;
- if (defined(obb)) {
- geoPosition = Cartographic.fromDegrees(
- obb.center[0],
- obb.center[1],
- obb.center[2]
- );
- } else {
- geoPosition = Cartographic.fromDegrees(mbs[0], mbs[1], mbs[2]);
- }
- // Offset bounding box position if we have a geoid service defined
- if (defined(this._dataProvider._geoidDataList) && defined(geoPosition)) {
- for (let i = 0; i < this._dataProvider._geoidDataList.length; i++) {
- const tile = this._dataProvider._geoidDataList[i];
- const projectedPos = tile.projection.project(geoPosition);
- if (
- projectedPos.x > tile.nativeExtent.west &&
- projectedPos.x < tile.nativeExtent.east &&
- projectedPos.y > tile.nativeExtent.south &&
- projectedPos.y < tile.nativeExtent.north
- ) {
- geoPosition.height += sampleGeoid(projectedPos.x, projectedPos.y, tile);
- break;
- }
- }
- }
- let boundingVolume = {};
- let position;
- let span = 0;
- if (defined(obb)) {
- boundingVolume = {
- box: [
- 0,
- 0,
- 0,
- obb.halfSize[0],
- 0,
- 0,
- 0,
- obb.halfSize[1],
- 0,
- 0,
- 0,
- obb.halfSize[2],
- ],
- };
- span = Math.max(
- Math.max(this._data.obb.halfSize[0], this._data.obb.halfSize[1]),
- this._data.obb.halfSize[2]
- );
- position = Ellipsoid.WGS84.cartographicToCartesian(geoPosition);
- } else {
- boundingVolume = {
- sphere: [0, 0, 0, mbs[3]],
- };
- position = Ellipsoid.WGS84.cartographicToCartesian(geoPosition);
- span = this._data.mbs[3];
- }
- span *= 2;
- // Compute the geometric error
- let metersPerPixel = Infinity;
- // Get the meters/pixel density required to pop the next LOD
- if (defined(this._data.lodThreshold)) {
- if (
- this._layer._data.nodePages.lodSelectionMetricType ===
- "maxScreenThresholdSQ"
- ) {
- const maxScreenThreshold = Math.sqrt(
- this._data.lodThreshold / (Math.PI * 0.25)
- );
- metersPerPixel = span / maxScreenThreshold;
- } else if (
- this._layer._data.nodePages.lodSelectionMetricType ===
- "maxScreenThreshold"
- ) {
- const maxScreenThreshold = this._data.lodThreshold;
- metersPerPixel = span / maxScreenThreshold;
- } else {
- // Other LOD selection types can only be used for point cloud data
- console.error("Invalid lodSelectionMetricType in Layer");
- }
- } else if (defined(this._data.lodSelection)) {
- for (
- let lodIndex = 0;
- lodIndex < this._data.lodSelection.length;
- lodIndex++
- ) {
- if (
- this._data.lodSelection[lodIndex].metricType === "maxScreenThreshold"
- ) {
- metersPerPixel = span / this._data.lodSelection[lodIndex].maxError;
- }
- }
- }
- if (metersPerPixel === Infinity) {
- metersPerPixel = 100000;
- }
- // Calculate the length of 16 pixels in order to trigger the screen space error
- const geometricError = metersPerPixel * 16;
- // Transformations
- const hpr = new HeadingPitchRoll(0, 0, 0);
- let orientation = Transforms.headingPitchRollQuaternion(position, hpr);
- if (defined(this._data.obb)) {
- orientation = new Quaternion(
- this._data.obb.quaternion[0],
- this._data.obb.quaternion[1],
- this._data.obb.quaternion[2],
- this._data.obb.quaternion[3]
- );
- }
- const rotationMatrix = Matrix3.fromQuaternion(orientation);
- const inverseRotationMatrix = Matrix3.inverse(rotationMatrix, new Matrix3());
- const globalTransform = new Matrix4(
- rotationMatrix[0],
- rotationMatrix[1],
- rotationMatrix[2],
- 0,
- rotationMatrix[3],
- rotationMatrix[4],
- rotationMatrix[5],
- 0,
- rotationMatrix[6],
- rotationMatrix[7],
- rotationMatrix[8],
- 0,
- position.x,
- position.y,
- position.z,
- 1
- );
- const inverseGlobalTransform = Matrix4.inverse(
- globalTransform,
- new Matrix4()
- );
- const localTransform = Matrix4.clone(globalTransform);
- if (defined(this._parent._globalTransform)) {
- Matrix4.multiply(
- globalTransform,
- this._parent._inverseGlobalTransform,
- localTransform
- );
- }
- this._globalTransform = globalTransform;
- this._inverseGlobalTransform = inverseGlobalTransform;
- this._inverseRotationMatrix = inverseRotationMatrix;
- // get children definition
- const childrenDefinition = [];
- for (let childIndex = 0; childIndex < this._children.length; childIndex++) {
- childrenDefinition.push(
- this._children[childIndex]._create3DTileDefinition()
- );
- }
- // Create a tile set
- const inPlaceTileDefinition = {
- children: childrenDefinition,
- refine: "REPLACE",
- boundingVolume: boundingVolume,
- transform: [
- localTransform[0],
- localTransform[4],
- localTransform[8],
- localTransform[12],
- localTransform[1],
- localTransform[5],
- localTransform[9],
- localTransform[13],
- localTransform[2],
- localTransform[6],
- localTransform[10],
- localTransform[14],
- localTransform[3],
- localTransform[7],
- localTransform[11],
- localTransform[15],
- ],
- content: {
- uri: defined(this._resource) ? this._resource.url : undefined,
- },
- geometricError: geometricError,
- };
- return inPlaceTileDefinition;
- };
- /**
- * @private
- */
- I3SNode.prototype._createI3SDecoderTask = async function (
- decodeI3STaskProcessor,
- data
- ) {
- // Prepare the data to send to the worker
- const parentData = data.geometryData._parent._data;
- const parentRotationInverseMatrix =
- data.geometryData._parent._inverseRotationMatrix;
- let longitude = 0.0;
- let latitude = 0.0;
- let height = 0.0;
- if (defined(parentData.obb)) {
- longitude = parentData.obb.center[0];
- latitude = parentData.obb.center[1];
- height = parentData.obb.center[2];
- } else if (defined(parentData.mbs)) {
- longitude = parentData.mbs[0];
- latitude = parentData.mbs[1];
- height = parentData.mbs[2];
- }
- const axisFlipRotation = Matrix3.fromRotationX(-CesiumMath.PI_OVER_TWO);
- const parentRotation = new Matrix3();
- Matrix3.multiply(
- axisFlipRotation,
- parentRotationInverseMatrix,
- parentRotation
- );
- const cartographicCenter = Cartographic.fromDegrees(
- longitude,
- latitude,
- height
- );
- const cartesianCenter = Ellipsoid.WGS84.cartographicToCartesian(
- cartographicCenter
- );
- const payload = {
- binaryData: data.geometryData._data,
- featureData:
- defined(data.featureData) && defined(data.featureData[0])
- ? data.featureData[0].data
- : undefined,
- schema: data.defaultGeometrySchema,
- bufferInfo: data.geometryData._geometryBufferInfo,
- ellipsoidRadiiSquare: Ellipsoid.WGS84.radiiSquared,
- url: data.url,
- geoidDataList: data.geometryData._dataProvider._geoidDataList,
- cartographicCenter: cartographicCenter,
- cartesianCenter: cartesianCenter,
- parentRotation: parentRotation,
- };
- const transferrableObjects = [];
- return decodeI3STaskProcessor.scheduleTask(payload, transferrableObjects);
- };
- /**
- * @private
- */
- I3SNode.prototype._createContentURL = async function () {
- let rawGltf = {
- scene: 0,
- scenes: [
- {
- nodes: [0],
- },
- ],
- nodes: [
- {
- name: "singleNode",
- },
- ],
- meshes: [],
- buffers: [],
- bufferViews: [],
- accessors: [],
- materials: [],
- textures: [],
- images: [],
- samplers: [],
- asset: {
- version: "2.0",
- },
- };
- const decodeI3STaskProcessor = await this._dataProvider.getDecoderTaskProcessor();
- // Load the geometry data
- const dataPromises = [this._loadGeometryData()];
- if (this._dataProvider.legacyVersion16) {
- dataPromises.push(this._loadFeatureData());
- }
- const that = this;
- return Promise.all(dataPromises).then(function () {
- // Binary glTF
- let generateGltfPromise = Promise.resolve();
- if (defined(that._geometryData) && that._geometryData.length > 0) {
- const parameters = {
- geometryData: that._geometryData[0],
- featureData: that._featureData,
- defaultGeometrySchema: that._layer._data.store.defaultGeometrySchema,
- url: that._geometryData[0].resource.url,
- tile: that._tile,
- };
- const promise = that._createI3SDecoderTask(
- decodeI3STaskProcessor,
- parameters
- );
- if (!defined(promise)) {
- // Postponed
- return;
- }
- generateGltfPromise = promise.then(function (result) {
- rawGltf = parameters.geometryData._generateGltf(
- result.meshData.nodesInScene,
- result.meshData.nodes,
- result.meshData.meshes,
- result.meshData.buffers,
- result.meshData.bufferViews,
- result.meshData.accessors
- );
- that._geometryData[0]._customAttributes =
- result.meshData._customAttributes;
- });
- }
- return generateGltfPromise.then(function () {
- const binaryGltfData = that._dataProvider._binarizeGltf(rawGltf);
- const glbDataBlob = new Blob([binaryGltfData], {
- type: "application/binary",
- });
- return URL.createObjectURL(glbDataBlob);
- });
- });
- };
- // Reimplement Cesium3DTile.prototype.requestContent so that
- // We get a chance to load our own gltf from I3S data
- Cesium3DTile.prototype._hookedRequestContent =
- Cesium3DTile.prototype.requestContent;
- /**
- * Requests the tile's content.
- * <p>
- * The request may not be made if the Cesium Request Scheduler can't prioritize it.
- * </p>
- *
- * @return {Promise<Cesium3DTileContent>|undefined} A promise that resolves when the request completes, or undefined if there is no request needed, or the request cannot be scheduled.
- * @private
- */
- Cesium3DTile.prototype.requestContent = function () {
- if (!this.tileset._isI3STileSet) {
- return this._hookedRequestContent();
- }
- if (!this._isLoading) {
- this._isLoading = true;
- return this._i3sNode
- ._createContentURL()
- .then((url) => {
- if (!defined(url)) {
- this._isLoading = false;
- return;
- }
- this._contentResource = new Resource({ url: url });
- return this._hookedRequestContent();
- })
- .then((content) => {
- this._isLoading = false;
- return content;
- });
- }
- };
- function bilinearInterpolate(tx, ty, h00, h10, h01, h11) {
- const a = h00 * (1 - tx) + h10 * tx;
- const b = h01 * (1 - tx) + h11 * tx;
- return a * (1 - ty) + b * ty;
- }
- function sampleMap(u, v, width, data) {
- const address = u + v * width;
- return data[address];
- }
- function sampleGeoid(sampleX, sampleY, geoidData) {
- const extent = geoidData.nativeExtent;
- let x =
- ((sampleX - extent.west) / (extent.east - extent.west)) *
- (geoidData.width - 1);
- let y =
- ((sampleY - extent.south) / (extent.north - extent.south)) *
- (geoidData.height - 1);
- const xi = Math.floor(x);
- let yi = Math.floor(y);
- x -= xi;
- y -= yi;
- const xNext = xi < geoidData.width ? xi + 1 : xi;
- let yNext = yi < geoidData.height ? yi + 1 : yi;
- yi = geoidData.height - 1 - yi;
- yNext = geoidData.height - 1 - yNext;
- const h00 = sampleMap(xi, yi, geoidData.width, geoidData.buffer);
- const h10 = sampleMap(xNext, yi, geoidData.width, geoidData.buffer);
- const h01 = sampleMap(xi, yNext, geoidData.width, geoidData.buffer);
- const h11 = sampleMap(xNext, yNext, geoidData.width, geoidData.buffer);
- let finalHeight = bilinearInterpolate(x, y, h00, h10, h01, h11);
- finalHeight = finalHeight * geoidData.scale + geoidData.offset;
- return finalHeight;
- }
- Object.defineProperties(Cesium3DTile.prototype, {
- /**
- * Gets the I3S Node for the tile.
- * @memberof Cesium3DTile.prototype
- * @type {string}
- */
- i3sNode: {
- get: function () {
- return this._i3sNode;
- },
- },
- });
- export default I3SNode;
|