diff --git a/src/Core/Prefab/Globe/GlobeLayer.js b/src/Core/Prefab/Globe/GlobeLayer.js index 0c717f06a5..1587b3dd19 100644 --- a/src/Core/Prefab/Globe/GlobeLayer.js +++ b/src/Core/Prefab/Globe/GlobeLayer.js @@ -57,7 +57,7 @@ class GlobeLayer extends TiledGeometryLayer { CRS.tms_3857, ]; const uvCount = config.tileMatrixSets.length; - const builder = new GlobeTileBuilder({ crs: 'EPSG:4978', uvCount }); + const builder = new GlobeTileBuilder({ uvCount }); super(id, object3d || new THREE.Group(), schemeTile, builder, config); diff --git a/src/Core/Prefab/Globe/GlobeTileBuilder.ts b/src/Core/Prefab/Globe/GlobeTileBuilder.ts index b03c0626c9..fd1ff1a5b7 100644 --- a/src/Core/Prefab/Globe/GlobeTileBuilder.ts +++ b/src/Core/Prefab/Globe/GlobeTileBuilder.ts @@ -16,35 +16,56 @@ const quatToAlignLongitude = new THREE.Quaternion(); const quatToAlignLatitude = new THREE.Quaternion(); const quatNormalToZ = new THREE.Quaternion(); -function WGS84ToOneSubY(latitude: number) { +/** Transforms a WGS84 latitude into a usable texture offset. */ +function WGS84ToOneSubY(latitude: number): number { return 1.0 - (0.5 - Math.log(Math.tan( PI_OV_FOUR + THREE.MathUtils.degToRad(latitude) * 0.5, )) * INV_TWO_PI); } +type Transform = { + /** Buffers for 2-part coordinate mapping operations. */ + coords: [Coordinates, Coordinates]; + position: THREE.Vector3; + dimension: THREE.Vector2; +}; + +/** Specialized parameters for the [GlobeTileBuilder]. */ export interface GlobeTileBuilderParams extends TileBuilderParams { + /** Number of rows of tiles, essentially the resolution of the globe. */ nbRow: number; + /** Offset of the second texture set. */ deltaUV1: number; + /** Transformation to align a tile's normal to the Z axis. */ quatNormalToZ: THREE.Quaternion; } +/** + * TileBuilder implementation for the purpose of generating globe (or more + * precisely ellipsoidal) tile arrangements. + */ export class GlobeTileBuilder implements TileBuilder { - private _crs: string; - private _transform: { - coords: Coordinates[]; - position: THREE.Vector3; - dimension: THREE.Vector2; - }; + private static _crs: string = 'EPSG:4978'; + private static _computeExtraOffset(params: GlobeTileBuilderParams): number { + const t = WGS84ToOneSubY(params.projected.latitude) * params.nbRow; + return (!isFinite(t) ? 0 : t) - params.deltaUV1; + } + + /** + * Buffer holding information about the tile/vertex currently being + * processed. + */ + private _transform: Transform; public computeExtraOffset?: (params: GlobeTileBuilderParams) => number; public get crs(): string { - return this._crs; + return GlobeTileBuilder._crs; } public constructor(options: { - crs: string, + /** Number of unaligned texture sets. */ uvCount: number, }) { this._transform = { @@ -56,9 +77,6 @@ implements TileBuilder { dimension: new THREE.Vector2(), }; - this._crs = options.crs; - // Order crs projection on tiles - // UV: Normalized coordinates (from degree) on the entire tile // EPSG:4326 // Offset: Float row coordinate from Pseudo mercator coordinates @@ -68,13 +86,6 @@ implements TileBuilder { } } - private static _computeExtraOffset(params: GlobeTileBuilderParams): number { - const t = WGS84ToOneSubY(params.projected.latitude) * params.nbRow; - return (!isFinite(t) ? 0 : t) - params.deltaUV1; - } - - // prepare params - // init projected object -> params.projected public prepare(params: TileBuilderParams): GlobeTileBuilderParams { const nbRow = 2 ** (params.level + 1.0); let st1 = WGS84ToOneSubY(params.extent.south); @@ -103,14 +114,12 @@ implements TileBuilder { return { ...params, ...newParams }; } - // get center tile in cartesian 3D public center(extent: Extent) { return extent.center(this._transform.coords[0]) .as(this.crs, this._transform.coords[1]) .toVector3(); } - // get position 3D cartesian public vertexPosition(position: THREE.Vector2): THREE.Vector3 { return this._transform.coords[0] .setFromValues(position.x, position.y) @@ -118,29 +127,24 @@ implements TileBuilder { .toVector3(this._transform.position); } - // get normal for last vertex public vertexNormal() { return this._transform.coords[1].geodesicNormal; } - // coord u tile to projected public uProject(u: number, extent: Extent): number { return extent.west + u * this._transform.dimension.x; } - // coord v tile to projected public vProject(v: number, extent: Extent): number { return extent.south + v * this._transform.dimension.y; } public computeShareableExtent(extent: Extent): ShareableExtent { - // Compute shareable extent to pool the geometries - // the geometry in common extent is identical to the existing input - // with a transformation (translation, rotation) - - // TODO: It should be possible to use equatorial plan symetrie, - // but we should be reverse UV on tile - // Common geometry is looking for only on longitude + // NOTE: It should be possible to take advantage of equatorial plane + // symmetry, for which we'd have to reverse the tile's UVs. + // This would halve the memory requirement when viewing a full globe, + // but that case is not that relevant for iTowns' usual use cases and + // the globe mesh memory usage is already inconsequential. const sizeLongitude = Math.abs(extent.west - extent.east) / 2; const shareableExtent = new Extent( extent.crs, @@ -148,9 +152,8 @@ implements TileBuilder { extent.south, extent.north, ); - // compute rotation to transform tile to position on ellipsoid - // this transformation takes into account the transformation of the - // parents + // Compute rotation to transform the tile to position on the ellispoid. + // This transformation takes the parents' transformation into account. const rotLon = THREE.MathUtils.degToRad( extent.west - shareableExtent.west, ); diff --git a/src/Core/Prefab/Planar/PlanarTileBuilder.ts b/src/Core/Prefab/Planar/PlanarTileBuilder.ts index 81466ba55f..faa4768ad2 100644 --- a/src/Core/Prefab/Planar/PlanarTileBuilder.ts +++ b/src/Core/Prefab/Planar/PlanarTileBuilder.ts @@ -17,12 +17,17 @@ type Transform = { normal: THREE.Vector3, }; +/** Specialized parameters for the [PlanarTileBuilder]. */ export interface PlanarTileBuilderParams extends TileBuilderParams { crs: string; uvCount?: number; nbRow: number; } +/** + * TileBuilder implementation for the purpose of generating planar + * tile arrangements. + */ export class PlanarTileBuilder implements TileBuilder { private _uvCount: number; private _transform: Transform; @@ -62,7 +67,7 @@ export class PlanarTileBuilder implements TileBuilder { // init projected object -> params.projected public prepare(params: TileBuilderParams): PlanarTileBuilderParams { const newParams = params as PlanarTileBuilderParams; - newParams.nbRow = 2 ** (params.zoom + 1.0); + newParams.nbRow = 2 ** (params.level + 1.0); newParams.projected = new Projected(); return newParams; } diff --git a/src/Core/Prefab/TileBuilder.ts b/src/Core/Prefab/TileBuilder.ts index b7fa510b17..ebd3691260 100644 --- a/src/Core/Prefab/TileBuilder.ts +++ b/src/Core/Prefab/TileBuilder.ts @@ -15,13 +15,16 @@ export type GpuBufferAttributes = { uvs: THREE.BufferAttribute[]; }; +/** + * Reference to a tile's extent with rigid transformations. + * Enables reuse of geometry, saving a bit of memory. + */ export type ShareableExtent = { shareableExtent: Extent; quaternion: THREE.Quaternion; position: THREE.Vector3; }; -// TODO: Check if this order is right // Ideally we split this into Vec2 and a simpler LatLon type // Somewhat equivalent to a light Coordinates class export class Projected extends THREE.Vector2 { @@ -47,14 +50,18 @@ export interface TileBuilderParams { disableSkirt: boolean; /** Whether to render the skirt. */ hideSkirt: boolean; + /** + * Cache-related. + * Tells the function whether to build or skip the index and uv buffers. + */ buildIndexAndUv_0: boolean; /** Number of segments (edge loops) inside tiles. */ segments: number; + // TODO: Move this out of the interface /** Buffer for projected points. */ projected: Projected; extent: Extent; level: number; - zoom: number; center: THREE.Vector3; } @@ -63,13 +70,26 @@ export interface TileBuilder { /** Convert builder-agnostic params to specialized ones. */ prepare(params: TileBuilderParams): SpecializedParams; + /** + * Computes final offset of the second texture set. + * Only relevant in the case of more than one texture sets. + */ computeExtraOffset?: (params: SpecializedParams) => number; - /** Get the center of the tile in 3D cartesian coordinates. */ + /** 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. */ vertexPosition(position: THREE.Vector2): THREE.Vector3; + /** Gets the geodesic normal of the last processed vertex. */ vertexNormal(): THREE.Vector3; + /** Project horizontal texture coordinate to world space. */ uProject(u: number, extent: Extent): number; + /** Project vertical texture coordinate to world space. */ vProject(v: number, extent: Extent): number; + /** + * Compute shareable extent to pool geometries together. + * The geometry of tiles on the same latitude is the same with an added + * rigid transform. + */ computeShareableExtent(extent: Extent): ShareableExtent; } @@ -86,7 +106,6 @@ export function newTileGeometry( `${builder.crs}_${params.disableSkirt ? 0 : 1}_${params.segments}`; let promiseGeometry = cacheTile.get(south, params.level, bufferKey); - // let promiseGeometry; // build geometry if doesn't exist if (!promiseGeometry) { diff --git a/src/Core/Prefab/computeBufferTileGeometry.ts b/src/Core/Prefab/computeBufferTileGeometry.ts index 54bae0df92..16e6c60f59 100644 --- a/src/Core/Prefab/computeBufferTileGeometry.ts +++ b/src/Core/Prefab/computeBufferTileGeometry.ts @@ -18,11 +18,11 @@ export type Buffers = { uvs: [Option, Option], }; -type TmpBuffers = Buffers & { +type BuffersAndSkirt = Buffers & { skirt: IndexArray, }; -function pickUintArraySize( +function getUintArrayConstructor( highestValue: number, ): Uint8ArrayConstructor | Uint16ArrayConstructor | Uint32ArrayConstructor { let picked = null; @@ -50,7 +50,7 @@ function allocateIndexBuffer( } const indexBufferSize = getBufferIndexSize(nSeg, params.disableSkirt); - const indexConstructor = pickUintArraySize(nVertex); + const indexConstructor = getUintArrayConstructor(nVertex); const tileLen = indexBufferSize; const skirtLen = 4 * nSeg; @@ -77,7 +77,7 @@ function allocateBuffers( nSeg: number, builder: TileBuilder, params: TileBuilderParams, -): TmpBuffers { +): BuffersAndSkirt { const { index, skirt, @@ -127,11 +127,12 @@ function initComputeUv1(value: number): (uv: Float32Array, id: number) => void { type ComputeUvs = [typeof computeUv0 | (() => void), ReturnType?]; +/** 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, -) { +): Buffers { // n seg, n+1 vert + <- skirt, n verts per side // <---------------> / | // +---+---+---+---+ | @@ -154,7 +155,7 @@ export function computeBuffers( throw new Error('Tile segments count is too big'); } - const outBuffers: TmpBuffers = allocateBuffers( + const outBuffers: BuffersAndSkirt = allocateBuffers( nTotalVertex, nSeg, builder, params, ); @@ -243,6 +244,7 @@ export function computeBuffers( } } + /** Copy passed indices at the desired index of the output index buffer. */ function bufferizeTri(id: number, va: number, vb: number, vc: number) { outBuffers.index![id + 0] = va; outBuffers.index![id + 1] = vb; diff --git a/src/Core/TileGeometry.ts b/src/Core/TileGeometry.ts index 696d82d135..c0a6eccf9a 100644 --- a/src/Core/TileGeometry.ts +++ b/src/Core/TileGeometry.ts @@ -10,7 +10,7 @@ import Cache from 'Core/Scheduler/Cache'; import OBB from 'Renderer/OBB'; type PartialTileBuilderParams = - Pick + Pick & Partial; function defaultBuffers( @@ -96,8 +96,7 @@ export class TileGeometry extends THREE.BufferGeometry { } /** - * Initialize reference count for this geometry. - * Idempotent operation. + * Initialize reference count for this geometry if it is currently null. * * @param cacheTile - The [Cache] used to store this geometry. * @param keys - The [south, level, epsg] key of this geometry. diff --git a/src/Renderer/OBB.js b/src/Renderer/OBB.js index cd8a5c2cac..f6f820d8e7 100644 --- a/src/Renderer/OBB.js +++ b/src/Renderer/OBB.js @@ -5,7 +5,7 @@ import Coordinates from 'Core/Geographic/Coordinates'; import CRS from 'Core/Geographic/Crs'; // get oriented bounding box of tile -const builder = new GlobeTileBuilder({ crs: 'EPSG:4978', uvCount: 1 }); +const builder = new GlobeTileBuilder({ uvCount: 1 }); const size = new THREE.Vector3(); const dimension = new THREE.Vector2(); const center = new THREE.Vector3(); diff --git a/test/unit/obb.js b/test/unit/obb.js index 05de9565cd..706e0929c9 100644 --- a/test/unit/obb.js +++ b/test/unit/obb.js @@ -83,7 +83,7 @@ describe('Planar tiles OBB computation', function () { }); }); describe('Ellipsoid tiles OBB computation', function () { - const builder = new GlobeTileBuilder({ crs: 'EPSG:4978', uvCount: 1 }); + const builder = new GlobeTileBuilder({ uvCount: 1 }); it('should compute globe-level 0 OBB correctly', function (done) { const extent = new Extent('EPSG:4326', -180, 0, -90, 90);