Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tiles: options enhancements #1475

Merged
merged 1 commit into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions examples/website/3d-tiles/examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ const SHOWCASE_EXAMPLES = {
ionAssetId: 55337,
ionAccessToken: ION_TOKEN,
maximumScreenSpaceError: 16
},
'Phillipines B3DM': {
ionAssetId: 34014,
ionAccessToken:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0MTQ0NTNiOC0wNzlmLTQ1ZGEtYjM3Yi05ZmJlY2FiMmRjYWMiLCJpZCI6MTMxNTEsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1NjI2OTQ3NTh9.tlqEVzzO25Itcla4jD17yywNFvAVM-aNVduzF6ss-1g'
},
'Phillipines Point Cloud': {
ionAssetId: 34013,
ionAccessToken:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0MTQ0NTNiOC0wNzlmLTQ1ZGEtYjM3Yi05ZmJlY2FiMmRjYWMiLCJpZCI6MTMxNTEsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1NjI2OTQ3NTh9.tlqEVzzO25Itcla4jD17yywNFvAVM-aNVduzF6ss-1g'
}
},
'Phillipines B3DM': {
ionAssetId: 34014,
ionAccessToken:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0MTQ0NTNiOC0wNzlmLTQ1ZGEtYjM3Yi05ZmJlY2FiMmRjYWMiLCJpZCI6MTMxNTEsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1NjI2OTQ3NTh9.tlqEVzzO25Itcla4jD17yywNFvAVM-aNVduzF6ss-1g'
},
'Phillipines Point Cloud': {
ionAssetId: 34013,
ionAccessToken:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0MTQ0NTNiOC0wNzlmLTQ1ZGEtYjM3Yi05ZmJlY2FiMmRjYWMiLCJpZCI6MTMxNTEsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1NjI2OTQ3NTh9.tlqEVzzO25Itcla4jD17yywNFvAVM-aNVduzF6ss-1g'
}
},
nearmap: {
Expand Down
15 changes: 9 additions & 6 deletions examples/website/3d-tiles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
"generate": "node ./generate-index/index.js"
},
"dependencies": {
"@deck.gl/core": "^8.1.4",
"@deck.gl/geo-layers": "^8.1.4",
"@deck.gl/layers": "^8.1.4",
"@deck.gl/mesh-layers": "^8.1.4",
"@deck.gl/react": "^8.1.4",
"@deck.gl/core": "8.4.6",
"@deck.gl/geo-layers": "8.4.6",
"@deck.gl/layers": "8.4.6",
"@deck.gl/mesh-layers": "8.4.6",
"@deck.gl/react": "8.4.6",
"@loaders.gl/3d-tiles": "2.2.0",
"@loaders.gl/core": "2.2.0",
"@loaders.gl/gltf": "2.2.0",
"@luma.gl/core": "^8.3.0",
"@luma.gl/constants": "8.4.4",
"@luma.gl/core": "8.4.4",
"@math.gl/core": "^3.3.0",
"@math.gl/geospatial": "^3.3.0",
"@probe.gl/stats-widget": "^3.3.0",
"marked": "^0.7.0",
"probe.gl": "^3.3.0",
Expand Down
7 changes: 5 additions & 2 deletions modules/tiles/docs/api-reference/tileset-3d.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ Parameters:
- `options.ellipsoid`=`Ellipsoid.WGS84` (`Ellipsoid`) - The ellipsoid determining the size and shape of the globe.
- `options.throttleRequests`=`true` (`Boolean`) - Determines whether or not to throttle tile fetching requests.
- `options.modelMatrix`=`Matrix4.IDENTITY` (`Matrix4`) - A 4x4 transformation matrix this transforms the entire tileset.
- `options.maximumMemoryUsage`=`512`] (`Number`) - The maximum amount of memory in MB that can be used by the tileset.
- `options.fetchOptions` - fetchOptions, i.e. headers, used to load tiles from tiling server
- `options.maximumMemoryUsage`=`512` (`Number`) - The maximum amount of memory in MB that can be used by the tileset.
- `options.viewDistanceScale`=`1.0` (`Number`) - A scaling factor for tile refinement. A lower value would cause lower level tiles to load. Useful for debugging and for restricting resource usage.
- `options.updateTransforms`=`true` (`Boolean`) - Always check if the tileset `modelMatrix` was updated. Set to `false` to improve performance when the tileset remains stationary in the scene.
- `options.loadOptions` - _loaders.gl_ options used when loading tiles from the tiling server. Includes `fetch` options such as authentication `headers`, worker options such as `maxConcurrency`, and options to other loaders such as `3d-tiles`, `gltf`, and `draco`.
- `options.contentLoader` = `null` (`Promise`) - An optional external async content loader for the tile. Once the promise resolves, a tile is regarded as _READY_ to be displayed on the viewport.
- `options.loadTiles`=`true` (`Boolean`) - Whether the tileset traverse and update tiles. Set this options to `false` during the run time to freeze the scene.

Callbacks:
Expand Down
3 changes: 2 additions & 1 deletion modules/tiles/src/tileset/helpers/tiles-3d-lod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ export function getTiles3DScreenSpaceError(tile, frameState, useParentLodMetric)
// Avoid divide by zero when viewer is inside the tile
const distance = Math.max(tile._distanceToCamera, 1e-7);
const {height, sseDenominator} = frameState;
let error = (lodMetricValue * height) / (distance * sseDenominator);
const {viewDistanceScale} = tileset.options;
let error = (lodMetricValue * height * (viewDistanceScale || 1.0)) / (distance * sseDenominator);

error -= getDynamicScreenSpaceError(tileset, distance);

Expand Down
16 changes: 12 additions & 4 deletions modules/tiles/src/tileset/tile-3d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,14 +327,19 @@ export default class TileHeader {
// The content can be a binary tile ot a JSON tileset
const loader = this.tileset.loader;
const options = {
fetch: this.tileset.fetchOptions,
[loader.id]: {
isTileset: this.type === 'json',
...this._getLoaderSpecificOptions(loader.id)
}
},
...this.tileset.loadOptions
};

this.content = await load(contentUrl, loader, options);

if (this.tileset.options.contentLoader) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have an onTileLoad callback. Do we need to add another callback? If we do, can we follow the convention and call it onTileContentLoad or similar?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the same as the onTile* callbacks which are essentially events.
The content loader promise must resolve before a tile can set

      this.contentState = TILE_CONTENT_STATE.READY;
      this._onContentLoaded();

And only then those callbacks may be fired.
Without this solution there is no way to use async loaders such as the three.js GLTFLoader without experiencing a race condition in which a tile is selected before it is actually on the render graph.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, thanks for clearing up the use case.

Would it make sense if we could declare it as onTileContentLoad(tile: Tile3D): Promise<void> and we wait for it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit hesitant because usually on* callbacks are not something you would expect would block execution. But I defer to you on this one.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I can see your point. Thoughts:

  • contentLoader risk being confusing in a framework that is specifically built around a concept of "loaders". I would have expected an option with that name to accept a loaders.gl loader.
  • Perhaps another name, ideally indicating some new naming convention?
  • We could also allow all the on... functions to return promises though this might lead to unintended synchronization for current users, if they are using async functions as callbacks.

When working with a big documented API the API audit tends to be a big part of the effort. This is why it is often best to make one PR for every addition, instead of one PR with 4 new props :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps tileProcessingHook or something of sort?
Understood about the heavy PRs. Will work to make those leaner in the future.

await this.tileset.options.contentLoader(this);
}

if (this._isTileset()) {
// Add tile headers for the nested tilset's subtree
// Async update of the tree should be fine since there would never be edits to the same node
Expand Down Expand Up @@ -382,12 +387,15 @@ export default class TileHeader {
}

const parent = this.parent;
const parentTransform = parent ? parent.computedTransform : this.tileset.modelMatrix;
const parentVisibilityPlaneMask = parent
? parent._visibilityPlaneMask
: CullingVolume.MASK_INDETERMINATE;

this._updateTransform(parentTransform);
if (this.tileset._traverser.options.updateTransforms) {
const parentTransform = parent ? parent.computedTransform : this.tileset.modelMatrix;
this._updateTransform(parentTransform);
}

this._distanceToCamera = this.distanceToTile(frameState);
this._screenSpaceError = this.getScreenSpaceError(frameState, false);
this._visibilityPlaneMask = this.visibility(frameState, parentVisibilityPlaneMask); // Use parent's plane mask to speed up visibility test
Expand Down
34 changes: 27 additions & 7 deletions modules/tiles/src/tileset/tileset-3d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ export type Tileset3DProps = {
attributions?: string[];
headers?: any;
loadTiles?: boolean;
fetchOptions?: {[key: string]: any};
loadOptions?: {[key: string]: any};
updateTransforms?: boolean;
maxRequests?: number;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also supply a requestSchedulerOptions object. It would avoid creating one more mapping of options and the documentation could be unified.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the only relevant request scheduler option is maxRequests.
This line actually slipped in here from the traversal PR which I still need to rebase.

viewDistanceScale?: number;
basePath?: string;
contentLoader?: (tile: Tile3D) => Promise<void>;
};

type Props = {
Expand All @@ -85,8 +89,11 @@ type Props = {
attributions: string[];
headers: any;
loadTiles: boolean;
fetchOptions: {[key: string]: any};
loadOptions: {[key: string]: any};
updateTransforms: boolean;
viewDistanceScale: number;
basePath: string;
contentLoader?: (tile: Tile3D) => Promise<void>;
i3s: {[key: string]: any};
};

Expand All @@ -108,15 +115,22 @@ const DEFAULT_PROPS: Props = {
onTileUnload: (tile) => {},
onTileError: (tile, message, url) => {},

// Optional async tile content loader
contentLoader: undefined,

// View distance scale modifier
viewDistanceScale: 1.0,

// TODO CESIUM
// The maximum screen space error used to drive level of detail refinement.
maximumScreenSpaceError: 8,

loadTiles: true,
updateTransforms: true,
viewportTraversersMap: null,

headers: {},
fetchOptions: {},
loadOptions: {},

token: '',
attributions: [],
Expand All @@ -140,7 +154,7 @@ const TILES_GPU_MEMORY = 'Tile Memory Use';
export default class Tileset3D {
// props: Tileset3DProps;
options: Props;
fetchOptions: {[key: string]: any};
loadOptions: {[key: string]: any};

type: string;
tileset: {[key: string]: any};
Expand Down Expand Up @@ -238,12 +252,18 @@ export default class Tileset3D {
this.refine = json.root.refine;

// TODO add to loader context?
this.fetchOptions = this.options.fetchOptions || {};
this.loadOptions = this.options.loadOptions || {};
if (this.options.headers) {
this.fetchOptions.headers = this.options.headers;
this.loadOptions.fetch = {
...this.loadOptions.fetch,
headers: this.options.headers
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make a breaking change and drop the headers option as it is now covered by loadOptions.fetch? This class is really complex, with so many options. Could certainly be a separate PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fine by me!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I created an API cleanup section here: #1245 where we can track proposals for API changes.

};
}
if (this.options.token) {
this.fetchOptions.token = this.options.token;
this.loadOptions.fetch = {
...this.loadOptions.fetch,
token: this.options.token
};
}

this.root = null;
Expand Down
2 changes: 2 additions & 0 deletions modules/tiles/src/tileset/traversers/tileset-traverser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type TilesetTraverserProps = {
export type Props = {
loadSiblings: boolean;
skipLevelOfDetail: boolean;
updateTransforms: boolean;
maximumScreenSpaceError: number;
onTraversalEnd: (frameState) => any;
viewportTraversersMap: {[key: string]: any};
Expand All @@ -24,6 +25,7 @@ export const DEFAULT_PROPS: Props = {
loadSiblings: false,
skipLevelOfDetail: false,
maximumScreenSpaceError: 2,
updateTransforms: true,
onTraversalEnd: () => {},
viewportTraversersMap: {},
basePath: ''
Expand Down