diff --git a/packages/Main/src/Converter/convertToTile.js b/packages/Main/src/Converter/convertToTile.ts similarity index 77% rename from packages/Main/src/Converter/convertToTile.js rename to packages/Main/src/Converter/convertToTile.ts index a687cd543e..1505f4bab1 100644 --- a/packages/Main/src/Converter/convertToTile.js +++ b/packages/Main/src/Converter/convertToTile.ts @@ -1,15 +1,32 @@ import * as THREE from 'three'; import TileMesh from 'Core/TileMesh'; import { LayeredMaterial } from 'Renderer/LayeredMaterial'; -import { newTileGeometry } from 'Core/Prefab/TileBuilder'; +import { newTileGeometry, TileBuilder, TileBuilderParams } from 'Core/Prefab/TileBuilder'; import ReferLayerProperties from 'Layer/ReferencingLayerProperties'; import { geoidLayerIsVisible } from 'Layer/GeoidLayer'; +import type { Extent } from '@itowns/geographic'; +import type { LayeredMaterialParameters } from 'Renderer/LayeredMaterial'; + const dimensions = new THREE.Vector2(); -function setTileFromTiledLayer(tile, tileLayer) { +// A simplified interface for TiledGeometryLayer. +// It is used to avoid a dependency on the full TiledGeometryLayer type. +interface TileLayerLike { + diffuse: THREE.Color; + showOutline: boolean; + isGlobeLayer: boolean; + segments: number; + disableSkirt: boolean; + hideSkirt: boolean; + tileMatrixSets: string[]; + materialOptions: LayeredMaterialParameters; + builder: TileBuilder; +} + +function setTileFromTiledLayer(tile: TileMesh, tileLayer: TileLayerLike) { if (tileLayer.diffuse) { - tile.material.diffuse = tileLayer.diffuse; + tile.material.setUniform('diffuse', tileLayer.diffuse); } if (__DEBUG__) { @@ -32,7 +49,7 @@ function setTileFromTiledLayer(tile, tileLayer) { } export default { - convert(requester, extent, layer) { + convert(requester: TileMesh, extent: Extent, layer: TileLayerLike) { const builder = layer.builder; const parent = requester; const level = (parent !== undefined) ? (parent.level + 1) : 0; diff --git a/packages/Main/src/Core/Prefab/Planar/PlanarTileBuilder.ts b/packages/Main/src/Core/Prefab/Planar/PlanarTileBuilder.ts index 2094912df4..707b0d7bb2 100644 --- a/packages/Main/src/Core/Prefab/Planar/PlanarTileBuilder.ts +++ b/packages/Main/src/Core/Prefab/Planar/PlanarTileBuilder.ts @@ -17,7 +17,6 @@ type Transform = { /** Specialized parameters for the [PlanarTileBuilder]. */ export interface PlanarTileBuilderParams extends TileBuilderParams { - crs: string; uvCount?: number; nbRow: number; } diff --git a/packages/Main/src/Core/Prefab/TileBuilder.ts b/packages/Main/src/Core/Prefab/TileBuilder.ts index 061818a4fb..d327e00abd 100644 --- a/packages/Main/src/Core/Prefab/TileBuilder.ts +++ b/packages/Main/src/Core/Prefab/TileBuilder.ts @@ -29,31 +29,33 @@ export type ShareableExtent = { position: THREE.Vector3; }; -export interface TileBuilderParams { +export interface TileBuilderPrepareParams { /** Whether to build the skirt. */ disableSkirt: boolean; /** Whether to render the skirt. */ hideSkirt: boolean; /** Number of segments (edge loops) inside tiles. */ segments: number; - // TODO: Move this out of the interface /** Buffer for projected points. */ - coordinates: Coordinates; extent: Extent; level: number; +} + +export interface TileBuilderParams extends TileBuilderPrepareParams { center: THREE.Vector3; + coordinates: Coordinates; } -export interface TileBuilder { +export interface TileBuilder { crs: string; /** Convert builder-agnostic params to specialized ones. */ - prepare(params: TileBuilderParams): SpecializedParams; + prepare(params: TileBuilderPrepareParams): SpecializedParams; /** * Computes final offset of the second texture set. * Only relevant in the case of more than one texture sets. */ - computeExtraOffset?: (params: SpecializedParams) => number; + computeExtraOffset?(params: SpecializedParams): number; /** Get the center of the current tile as a 3D vector. */ center(extent: Extent): THREE.Vector3; /** Converts an x/y tile-space position to its equivalent in 3D space. */ @@ -73,8 +75,8 @@ export interface TileBuilder { } export function newTileGeometry( - builder: TileBuilder, - params: TileBuilderParams, + builder: TileBuilder, + params: TileBuilderPrepareParams, ) { const { shareableExtent, quaternion, position } = builder.computeShareableExtent(params.extent); @@ -94,7 +96,7 @@ export function newTileGeometry( cacheTile.set(key, promiseGeometry); params.extent = shareableExtent; - params.center = builder.center(params.extent).clone(); + const center = builder.center(params.extent).clone(); // Read previously cached values (index and uv.wgs84 only // depend on the # of triangles) let cachedBuffers = cacheBuffer.get(bufferKey); @@ -103,7 +105,7 @@ export function newTileGeometry( try { buffers = computeBuffers( builder, - params, + { ...params, center }, cachedBuffers !== undefined ? { index: cachedBuffers.index.array as diff --git a/packages/Main/src/Core/Prefab/computeBufferTileGeometry.ts b/packages/Main/src/Core/Prefab/computeBufferTileGeometry.ts index bffb7fe84d..4137c0b3a7 100644 --- a/packages/Main/src/Core/Prefab/computeBufferTileGeometry.ts +++ b/packages/Main/src/Core/Prefab/computeBufferTileGeometry.ts @@ -1,5 +1,5 @@ -import type { TileBuilder, TileBuilderParams } from 'Core/Prefab/TileBuilder'; import * as THREE from 'three'; +import type { TileBuilder, TileBuilderPrepareParams } from 'Core/Prefab/TileBuilder'; export function getBufferIndexSize(segments: number, noSkirt: boolean): number { const triangles = (segments) * (segments) * 2 @@ -48,7 +48,7 @@ type BufferCache = { function allocateIndexBuffer( nVertex: number, nSeg: number, - params: TileBuilderParams, + params: { disableSkirt: boolean }, cache?: BufferCache['index'], ): { index: IndexArray, skirt: IndexArray } { const indexBufferSize = getBufferIndexSize(nSeg, params.disableSkirt); @@ -66,7 +66,7 @@ function allocateIndexBuffer( tileLen // Skirt + (params.disableSkirt ? 0 : skirtLen) - ) * indexConstructor!.BYTES_PER_ELEMENT); + ) * indexConstructor.BYTES_PER_ELEMENT); const index = new indexConstructor(indexBuffer); const skirt = !params.disableSkirt @@ -82,8 +82,8 @@ function allocateIndexBuffer( function allocateBuffers( nVertex: number, nSeg: number, - builder: TileBuilder, - params: TileBuilderParams, + builder: TileBuilder, + params: { disableSkirt: boolean }, cache?: BufferCache, ): BuffersAndSkirt { const { @@ -133,11 +133,15 @@ function initComputeUv1(value: number): (uv: Float32Array, id: number) => void { type ComputeUvs = [typeof computeUv0 | (() => void), ReturnType?]; +interface ComputeBuffersParams extends TileBuilderPrepareParams { + center: THREE.Vector3; +} + /** Compute buffers describing a tile according to a builder and its params. */ // TODO: Split this even further into subfunctions export function computeBuffers( - builder: TileBuilder, - params: TileBuilderParams, + builder: TileBuilder, + params: ComputeBuffersParams, cache?: BufferCache, ): Buffers { // n seg, n+1 vert + <- skirt, n verts per side @@ -171,16 +175,16 @@ export function computeBuffers( const computeUvs: ComputeUvs = [cache === undefined ? computeUv0 : () => { }]; - params = builder.prepare(params); + const preparedParams = builder.prepare(params); for (let y = 0; y <= nSeg; y++) { const v = y / nSeg; - params.coordinates.y = builder.vProject(v, params.extent); + preparedParams.coordinates.y = builder.vProject(v, params.extent); if (builder.computeExtraOffset !== undefined) { computeUvs[1] = initComputeUv1( - builder.computeExtraOffset(params) as number, + builder.computeExtraOffset(preparedParams) as number, ); } @@ -188,19 +192,19 @@ export function computeBuffers( const u = x / nSeg; const id_m3 = (y * nVertex + x) * 3; - params.coordinates.x = builder.uProject(u, params.extent); + preparedParams.coordinates.x = builder.uProject(u, preparedParams.extent); - const vertex = builder.vertexPosition(params.coordinates); + const vertex = builder.vertexPosition(preparedParams.coordinates); const normal = builder.vertexNormal(); // move geometry to center world - vertex.sub(params.center); + vertex.sub(preparedParams.center); // align normal to z axis // HACK: this check style is not great - if ('quatNormalToZ' in params) { + if ('quatNormalToZ' in preparedParams) { const quat = - params.quatNormalToZ as THREE.Quaternion; + preparedParams.quatNormalToZ as THREE.Quaternion; vertex.applyQuaternion(quat); normal.applyQuaternion(quat); } @@ -217,7 +221,7 @@ export function computeBuffers( } // Fill skirt index buffer - if (cache === undefined && !params.disableSkirt) { + if (cache === undefined && !preparedParams.disableSkirt) { for (let x = 0; x < nVertex; x++) { // --------> // 0---1---2 diff --git a/packages/Main/src/Core/TileGeometry.ts b/packages/Main/src/Core/TileGeometry.ts index c7e229b886..7cfc627c50 100644 --- a/packages/Main/src/Core/TileGeometry.ts +++ b/packages/Main/src/Core/TileGeometry.ts @@ -9,13 +9,14 @@ import { LRUCache } from 'lru-cache'; import OBB from 'Renderer/OBB'; -type PartialTileBuilderParams = - Pick - & Partial; +type PartialParams< + Params extends TileBuilderParams, + Keys extends keyof Params = 'extent' | 'level' +> = Pick & Partial; function defaultBuffers( - builder: TileBuilder, - params: PartialTileBuilderParams, + builder: TileBuilder, + params: PartialParams, ): GpuBufferAttributes { const fullParams = { disableSkirt: false, @@ -70,8 +71,8 @@ export class TileGeometry extends THREE.BufferGeometry { } | null; public constructor( - builder: TileBuilder, - params: TileBuilderParams, + builder: TileBuilder, + params: PartialParams, bufferAttributes: GpuBufferAttributes = defaultBuffers(builder, params), ) { super(); @@ -88,9 +89,7 @@ export class TileGeometry extends THREE.BufferGeometry { this.computeBoundingBox(); this.OBB = null; - if (params.hideSkirt) { - this.hideSkirt = params.hideSkirt; - } + this.hideSkirt = params.hideSkirt ?? false; this._refCount = null; } @@ -111,7 +110,7 @@ export class TileGeometry extends THREE.BufferGeometry { * @param keys - The [south, level, epsg] key of this geometry. */ public initRefCount( - cacheTile: LRUCache>, + cacheTile: LRUCache>, key: string, ): void { if (this._refCount !== null) { diff --git a/packages/Main/src/Core/TileMesh.js b/packages/Main/src/Core/TileMesh.js deleted file mode 100644 index ba7af6c9a3..0000000000 --- a/packages/Main/src/Core/TileMesh.js +++ /dev/null @@ -1,121 +0,0 @@ -import * as THREE from 'three'; -import { geoidLayerIsVisible } from 'Layer/GeoidLayer'; -import { tiledCovering } from 'Core/Tile/Tile'; - -/** - * A TileMesh is a THREE.Mesh with a geometricError and an OBB - * The objectId property of the material is the with the id of the TileMesh - * @param {TileGeometry} geometry - the tile geometry - * @param {THREE.Material} material - a THREE.Material compatible with THREE.Mesh - * @param {Layer} layer - the layer the tile is added to - * @param {Extent} extent - the tile extent - * @param {?number} level - the tile level (default = 0) - */ -class TileMesh extends THREE.Mesh { - #_tms = new Map(); - #visible = true; - constructor(geometry, material, layer, extent, level = 0) { - super(geometry, material); - - if (!extent) { - throw new Error('extent is mandatory to build a TileMesh'); - } - this.layer = layer; - this.extent = extent; - this.extent.zoom = level; - - this.level = level; - - this.material.setUniform('objectId', this.id); - - this.obb = this.geometry.OBB.clone(); - this.boundingSphere = new THREE.Sphere(); - this.obb.box3D.getBoundingSphere(this.boundingSphere); - - for (const tms of layer.tileMatrixSets) { - this.#_tms.set(tms, tiledCovering(this.extent, tms)); - } - - this.frustumCulled = false; - this.matrixAutoUpdate = false; - this.rotationAutoUpdate = false; - - this.layerUpdateState = {}; - this.isTileMesh = true; - - this.geoidHeight = 0; - - this.link = {}; - - Object.defineProperty(this, 'visible', { - get() { return this.#visible; }, - set(v) { - if (this.#visible != v) { - this.#visible = v; - this.dispatchEvent({ type: v ? 'shown' : 'hidden' }); - } - }, - }); - } - /** - * If specified, update the min and max elevation of the OBB - * and updates accordingly the bounding sphere and the geometric error - * - * @param {Object} elevation - * @param {number} [elevation.min] - * @param {number} [elevation.max] - * @param {number} [elevation.scale] - */ - setBBoxZ(elevation) { - elevation.geoidHeight = geoidLayerIsVisible(this.layer) ? this.geoidHeight : 0; - this.obb.updateZ(elevation); - if (this.horizonCullingPointElevationScaled) { - this.horizonCullingPointElevationScaled.setLength(this.obb.z.delta + this.horizonCullingPoint.length()); - } - this.obb.box3D.getBoundingSphere(this.boundingSphere); - } - - getExtentsByProjection(crs) { - return this.#_tms.get(crs); - } - - /** - * Search for a common ancestor between this tile and another one. It goes - * through parents on each side until one is found. - * - * @param {TileMesh} tile - * - * @return {TileMesh} the resulting common ancestor - */ - findCommonAncestor(tile) { - if (!tile) { - return undefined; - } - if (tile.level == this.level) { - if (tile.id == this.id) { - return tile; - } else if (tile.level != 0) { - return this.parent.findCommonAncestor(tile.parent); - } else { - return undefined; - } - } else if (tile.level < this.level) { - return this.parent.findCommonAncestor(tile); - } else { - return this.findCommonAncestor(tile.parent); - } - } - - /** - * An optional callback that is executed immediately before a 3D object is rendered. - * - * @param {THREE.WebGLRenderer} renderer - The renderer used to render textures. - */ - onBeforeRender(renderer) { - if (this.material.layersNeedUpdate) { - this.material.updateLayersUniforms(renderer); - } - } -} - -export default TileMesh; diff --git a/packages/Main/src/Core/TileMesh.ts b/packages/Main/src/Core/TileMesh.ts new file mode 100644 index 0000000000..f13cefb9b2 --- /dev/null +++ b/packages/Main/src/Core/TileMesh.ts @@ -0,0 +1,152 @@ +import * as THREE from 'three'; +import { geoidLayerIsVisible } from 'Layer/GeoidLayer'; +import { tiledCovering } from 'Core/Tile/Tile'; + +import type { Extent } from '@itowns/geographic'; +import type { TileGeometry } from 'Core/TileGeometry'; +import type Tile from 'Core/Tile/Tile'; +import OBB from 'Renderer/OBB'; +import type { LayeredMaterial } from 'Renderer/LayeredMaterial'; +import type LayerUpdateState from 'Layer/LayerUpdateState'; + +interface TileLayerLike { + tileMatrixSets: string[]; +} + +/** + * A TileMesh is a THREE.Mesh with a geometricError and an OBB. + * The objectId property of the layered material is assigned to the id of the + * TileMesh. + * @param geometry - The tile geometry + * @param material - A THREE.Material compatible with THREE.Mesh + * @param layer - The layer the tile is added to + * @param extent - The tile extent + * @param level - The tile level (default = 0) + */ +class TileMesh extends THREE.Mesh { + readonly isTileMesh: true; + + layer: TileLayerLike; + extent: Extent; + level: number; + obb: OBB; + boundingSphere: THREE.Sphere; + layerUpdateState: Record; + geoidHeight: number; + link: Record; + horizonCullingPoint: THREE.Vector3 | undefined; + horizonCullingPointElevationScaled: THREE.Vector3 | undefined; + + private _tms = new Map(); + + constructor( + geometry: TileGeometry, + material: LayeredMaterial, + layer: TileLayerLike, + extent: Extent, + level = 0, + ) { + super(geometry, material); + + if (!extent) { + throw new Error('extent is mandatory to build a TileMesh'); + } + this.layer = layer; + this.extent = extent; + + this.level = level; + + this.material.setUniform('objectId', this.id); + + this.obb = this.geometry.OBB!.clone(); + this.boundingSphere = new THREE.Sphere(); + this.obb.box3D.getBoundingSphere(this.boundingSphere); + + for (const tms of layer.tileMatrixSets) { + this._tms.set(tms, tiledCovering(this.extent, tms)); + } + + this.frustumCulled = false; + this.matrixAutoUpdate = false; + + this.layerUpdateState = {}; + this.isTileMesh = true; + + this.geoidHeight = 0; + + this.link = {}; + + let _visible = true; + Object.defineProperty(this, 'visible', { + get() { return _visible; }, + set(v) { + if (_visible != v) { + _visible = v; + this.dispatchEvent({ type: v ? 'shown' : 'hidden' }); + } + }, + }); + } + + /** + * If specified, updates the min and max elevation of the OBB + * and updates accordingly the bounding sphere and the geometric error. + * + * @param elevation - Elevation parameters + */ + setBBoxZ(elevation: { min?: number, max?: number, scale?: number, geoidHeight?: number }) { + elevation.geoidHeight = geoidLayerIsVisible(this.layer) ? this.geoidHeight : 0; + this.obb.updateZ(elevation); + if (this.horizonCullingPointElevationScaled && this.horizonCullingPoint) { + this.horizonCullingPointElevationScaled.setLength( + this.obb.z.delta + this.horizonCullingPoint.length(), + ); + } + this.obb.box3D.getBoundingSphere(this.boundingSphere); + } + + getExtentsByProjection(tms: string) { + return this._tms.get(tms); + } + + /** + * Finds the common ancestor between this tile and another one. It goes + * through parents on each side until one is found. + * + * @param tile - The tile to find common ancestor with + * @returns The common ancestor between those two tiles, or undefined if + * not found + */ + findCommonAncestor(tile: TileMesh): TileMesh | undefined { + if (!tile) { + return undefined; + } + if (tile.level == this.level) { + if (tile.id == this.id) { + return tile; + } else if (tile.level != 0) { + return (this.parent as TileMesh)?.findCommonAncestor(tile.parent as TileMesh); + } else { + return undefined; + } + } else if (tile.level < this.level) { + return (this.parent as TileMesh)?.findCommonAncestor(tile as TileMesh); + } else { + return this.findCommonAncestor(tile.parent as TileMesh); + } + } + + /** + * An optional callback that is executed immediately before a 3D object + * is rendered. + * + * @param renderer - The renderer used to render textures. + */ + override onBeforeRender(renderer: THREE.WebGLRenderer) { + if (this.material.layersNeedUpdate) { + this.material.updateLayersUniforms(renderer); + } + } +} + +export default TileMesh; diff --git a/packages/Main/src/Layer/ColorLayer.js b/packages/Main/src/Layer/ColorLayer.js index ec45c3a748..5e022ff5a1 100644 --- a/packages/Main/src/Layer/ColorLayer.js +++ b/packages/Main/src/Layer/ColorLayer.js @@ -72,13 +72,18 @@ class ColorLayer extends RasterLayer { * * `2`: white color to invisible effect. * * `3`: custom shader effect (defined `ShaderChunk.customBodyColorLayer` and `ShaderChunk.customHeaderColorLayer`). * @param {number} [config.effect_parameter=1.0] - amount value used with effect applied on raster color. - * @param {(boolean|Object)} [config.addLabelLayer] - Used to tell if this layer has - * labels to display from its data. For example, it needs to be set to `true` - * for a layer with vector tiles. If it's `true` a new `LabelLayer` is added and attached to this `Layer`. - * You can also configure it with {@link LabelLayer} options described below such as: `addLabelLayer: { performance: true }`. - * @param {boolean} [config.addLabelLayer.performance=false] - In case label layer adding, so remove labels that have no chance of being visible. - * Indeed, even in the best case, labels will never be displayed. By example, if there's many labels. - * @param {boolean} [config.addLabelLayer.forceClampToTerrain=false] - use elevation layer to clamp label on terrain. + * @param {(boolean|Object)} [config.addLabelLayer] - Used to indicate + * whether this layer can display labels from the data. For example, it + * needs to be set for vector tile layers if you want to display labels. + * If set, a new `LabelLayer` is added and attached to this layer. + * You can also configure it with {@link LabelLayer} options described below + * such as: `addLabelLayer: { performance: true }`. + * @param {boolean} [config.addLabelLayer.performance=false] - When adding + * a label layer, remove all labels that have no chance of being visible. + * Even if a label is visible, it may never be displayed if there are too + * many labels in the same screen area. + * @param {boolean} [config.addLabelLayer.forceClampToTerrain=false] - Use + * elevation layer to clamp labels to terrain. * * @example * // Create a ColorLayer diff --git a/packages/Main/src/Layer/LayerUpdateState.ts b/packages/Main/src/Layer/LayerUpdateState.ts index d2495cd743..0ccb9e75c6 100644 --- a/packages/Main/src/Layer/LayerUpdateState.ts +++ b/packages/Main/src/Layer/LayerUpdateState.ts @@ -42,10 +42,19 @@ class LayerUpdateState { }; } + /** + * Checks if the update has finished successfully. + */ hasFinished() { return UPDATE_STATE.FINISHED == this.state; } + /** + * Checks if an update can be attempted based on the current update state. + * + * @param timestamp - Current timestamp in milliseconds (defaults to + * Date.now()). + */ canTryUpdate(timestamp = Date.now()) { switch (this.state) { case UPDATE_STATE.IDLE: { @@ -64,6 +73,9 @@ class LayerUpdateState { } } + /** + * Gives the number of seconds to wait before the next retry attempt. + */ secondsUntilNextTry() { if (this.state !== UPDATE_STATE.ERROR) { return 0; @@ -74,19 +86,36 @@ class LayerUpdateState { return PAUSE_BETWEEN_ERRORS[idx]; } + /** + * Marks the beginning of a new update attempt. + */ newTry() { this.state = UPDATE_STATE.PENDING; } + /** + * Marks the update as successful. It resets the error tracking. + */ success() { this.lastErrorTimestamp = 0; this.state = UPDATE_STATE.IDLE; } + /** + * Marks the update as permanently finished, preventing further update + * attempts. + */ noMoreUpdatePossible() { this.state = UPDATE_STATE.FINISHED; } + /** + * Handles the case where no data is available for the requested level. + * Updates the lowest level error tracking for future retry attempts. + * + * @param failureParams - The current context of the failure (this includes + * the current updated level). + */ noData(failureParams: { targetLevel: number }) { this.state = UPDATE_STATE.IDLE; this.failureParams.lowestLevelError = Math.min( @@ -95,6 +124,15 @@ class LayerUpdateState { ); } + /** + * Handles update failures. An error is either definitive or retryable (up + * to four attempts). + * + * @param timestamp - The timestamp when the failure occurred. + * @param definitive - Whether this error stops the update process. + * @param failureParams - The current context of the failure (this includes + * the current updated level). + */ failure(timestamp: number, definitive: boolean, failureParams: { targetLevel: number }) { if (failureParams && failureParams.targetLevel != undefined) { this.failureParams.lowestLevelError = Math.min( @@ -107,6 +145,9 @@ class LayerUpdateState { this.errorCount++; } + /** + * Checks if the layer is currently in an error state. + */ inError() { return this.state == UPDATE_STATE.DEFINITIVE_ERROR || this.state == UPDATE_STATE.ERROR; } diff --git a/packages/Main/src/Process/FeatureProcessing.js b/packages/Main/src/Process/FeatureProcessing.js index 1a49308fa9..721ac63e47 100644 --- a/packages/Main/src/Process/FeatureProcessing.js +++ b/packages/Main/src/Process/FeatureProcessing.js @@ -29,9 +29,14 @@ export default { return; } - const extentsDestination = node.getExtentsByProjection(layer.source.crs) || [node.extent]; - - const zoomDest = extentsDestination[0].zoom; + let extentsDestination = node.getExtentsByProjection(layer.source.crs); + let zoomDest; + if (!extentsDestination) { + extentsDestination = [node.extent]; // of type Extent + zoomDest = node.level; + } else { + zoomDest = extentsDestination[0].zoom; + } // check if it's tile level is equal to display level layer. // TO DO updata at all level asked diff --git a/packages/Main/src/Renderer/OBB.js b/packages/Main/src/Renderer/OBB.ts similarity index 55% rename from packages/Main/src/Renderer/OBB.js rename to packages/Main/src/Renderer/OBB.ts index b42058dac6..8e27db30b5 100644 --- a/packages/Main/src/Renderer/OBB.js +++ b/packages/Main/src/Renderer/OBB.ts @@ -3,47 +3,49 @@ import { TileGeometry } from 'Core/TileGeometry'; import { GlobeTileBuilder } from 'Core/Prefab/Globe/GlobeTileBuilder'; import { CRS, Coordinates } from '@itowns/geographic'; +import type { Extent } from '@itowns/geographic'; + // get oriented bounding box of tile const builder = new GlobeTileBuilder({ uvCount: 1 }); const size = new THREE.Vector3(); const dimension = new THREE.Vector2(); const center = new THREE.Vector3(); const coord = new Coordinates('EPSG:4326', 0, 0, 0); -let obb; +let _obb: OBB; // it could be considered to remove THREE.Object3D extend. /** - * Oriented bounding box - * @extends THREE.Object3D + * Represents an oriented bounding box. */ class OBB extends THREE.Object3D { + box3D: THREE.Box3; + natBox: THREE.Box3; + z: { min: number, max: number, scale: number, delta: number }; + /** - * @param {THREE.Vector3} min representing the lower (x, y, z) boundary of the box. Default is ( + Infinity, + Infinity, + Infinity ). - * @param {THREE.Vector3} max representing the lower upper (x, y, z) boundary of the box. Default is ( - Infinity, - Infinity, - Infinity ). + * @param min - (optional) A {@link THREE.Vector3} representing the lower + * (x, y, z) boundary of the box. + * Default is ( + Infinity, + Infinity, + Infinity ). + * @param max - (optional) A {@link THREE.Vector3} representing the upper + * (x, y, z) boundary of the box. + * Default is ( - Infinity, - Infinity, - Infinity ). */ - constructor(min = new THREE.Vector3(+Infinity, +Infinity, +Infinity), max = new THREE.Vector3(-Infinity, -Infinity, -Infinity)) { + constructor( + min = new THREE.Vector3(+Infinity, +Infinity, +Infinity), + max = new THREE.Vector3(-Infinity, -Infinity, -Infinity), + ) { super(); this.box3D = new THREE.Box3(min.clone(), max.clone()); this.natBox = this.box3D.clone(); - this.z = { min: 0, max: 0, scale: 1.0 }; - } - - /** - * Creates a new instance of the object with same properties than original. - * - * @return {OBB} Copy of this object. - */ - clone() { - return new OBB().copy(this); + this.z = { min: 0, max: 0, scale: 1.0, delta: 0 }; } /** - * Copy the property of OBB + * Copies the property from cOBB to this OBB. * - * @param {OBB} cOBB OBB to copy - * @return {OBB} the copy + * @param cOBB - OBB to copy */ - copy(cOBB) { + override copy(cOBB: OBB): this { super.copy(cOBB); this.box3D.copy(cOBB.box3D); this.natBox.copy(cOBB.natBox); @@ -54,21 +56,21 @@ class OBB extends THREE.Object3D { } /** - * Update z min, z max and z scale of oriented bounding box + * Updates the z min, z max and z scale of oriented bounding box. * - * @param {Object} [elevation={}] - * @param {number} [elevation.min] The minimum of oriented bounding box - * @param {number} [elevation.max] The maximum of oriented bounding box - * @param {number} [elevation.scale] The scale of oriented bounding box Z axis - * @param {number} [elevation.geoidHeight] The geoid height added to ellipsoid. + * @param elevation - Elevation parameters */ - updateZ(elevation = {}) { + updateZ(elevation: { min?: number, max?: number, scale?: number, geoidHeight?: number } = {}) { this.z.min = elevation.min ?? this.z.min; this.z.max = elevation.max ?? this.z.max; - this.z.scale = elevation.scale > 0 ? elevation.scale : this.z.scale; + this.z.scale = elevation.scale && elevation.scale > 0 ? elevation.scale : this.z.scale; this.z.delta = Math.abs(this.z.max - this.z.min) * this.z.scale; + // TODO: why not add the geoid height to the min and max parameters? + // The implementation of GeoidLayer is leaking here. + // This will be fixed when geoid layers will be considered as elevation + // layers. const geoidHeight = elevation.geoidHeight || 0; this.box3D.min.z = this.natBox.min.z + this.z.min * this.z.scale + geoidHeight; @@ -76,12 +78,13 @@ class OBB extends THREE.Object3D { } /** - * Determines if the sphere is above the XY space of the box + * Determines if the sphere is above the XY space of the box. * - * @param {Sphere} sphere The sphere - * @return {boolean} True if sphere is above the XY space of the box, False otherwise. + * @param sphere - The sphere + * @returns true if the sphere is above the XY space of the box, false + * otherwise. */ - isSphereAboveXYBox(sphere) { + isSphereAboveXYBox(sphere: THREE.Sphere): boolean { const localSpherePosition = this.worldToLocal(sphere.center); // get obb closest point to sphere center by clamping const x = Math.max(this.box3D.min.x, Math.min(localSpherePosition.x, this.box3D.max.x)); @@ -95,19 +98,27 @@ class OBB extends THREE.Object3D { } /** - * Compute OBB from extent. + * Computes the OBB from an extent. * The OBB resulted can be only in the system 'EPSG:3946'. * - * @param {Extent} extent The extent (with crs 'EPSG:4326') to compute oriented bounding box - * @param {number} minHeight The minimum height of OBB - * @param {number} maxHeight The maximum height of OBB - * @return {OBB} return this object + * @param extent - The extent (with crs 'EPSG:4326') to compute oriented + * bounding box + * @param minHeight - The minimum height of OBB + * @param maxHeight - The maximum height of OBB + * @returns return this object */ - setFromExtent(extent, minHeight = extent.min || 0, maxHeight = extent.max || 0) { + setFromExtent(extent: Extent, minHeight = 0, maxHeight = 0): this { if (extent.crs == 'EPSG:4326') { - const { shareableExtent, quaternion, position } = builder.computeShareableExtent(extent); + const { + shareableExtent, + quaternion, + position, + } = builder.computeShareableExtent(extent); // Compute the minimum count of segment to build tile - const segments = Math.max(Math.floor(shareableExtent.planarDimensions(dimension).x / 90 + 1), 2); + const segments = Math.max( + Math.floor(shareableExtent.planarDimensions(dimension).x / 90 + 1), + 2, + ); const paramsGeometry = { extent: shareableExtent, level: 0, @@ -116,9 +127,11 @@ class OBB extends THREE.Object3D { }; const geometry = new TileGeometry(builder, paramsGeometry); - obb.box3D.copy(geometry.boundingBox); - obb.natBox.copy(geometry.boundingBox); - this.copy(obb); + if (geometry.boundingBox) { + _obb.box3D.copy(geometry.boundingBox); + _obb.natBox.copy(geometry.boundingBox); + this.copy(_obb); + } this.updateZ({ min: minHeight, max: maxHeight }); this.position.copy(position); @@ -137,6 +150,6 @@ class OBB extends THREE.Object3D { } } -obb = new OBB(); +_obb = new OBB(); export default OBB;