UNPKG

maplibre-gl

Version:

BSD licensed community fork of mapbox-gl, a WebGL interactive maps library

220 lines (192 loc) 9.88 kB
import {now} from '../util/time_control'; import {getEdgeTiles} from '../util/util'; import {FadingDirections, FadingRoles, type Tile} from './tile'; import {type OverscaledTileID} from './tile_id'; import {type InViewTiles} from './tile_manager_in_view_tiles'; export function isRasterType(type: string): boolean { return type === 'raster' || type === 'image' || type === 'video'; } /** * Designate fading bases and parents using a many-to-one relationship where the lower children fade in/out * with their parents. Raster shaders are not currently designed for a one-to-many fade relationship. * * Tiles that are candidates for fading out must be loaded and rendered tiles, as loading a tile to then * fade it out would not appear smoothly. The first source of truth for tile fading always starts at the * ideal tile, which continually changes on map adjustment. The state of the previously rendered ideal * tile plane indicates which direction to fade each part of the newer ideal plane (with varying z). * * For a pitched map, the back of the map can have decreasing zooms while the front can have increasing zooms. * Fade logic must therefore adapt dynamically based on the previously rendered ideal tile set. */ export function updateFadingTiles( inViewTiles: InViewTiles, idealTileIDs: OverscaledTileID[], retain: Record<string, OverscaledTileID>, maxFadingAncestorLevels: number, sourceMinZoom: number, sourceMaxZoom: number, rasterFadeDuration: number) { const currentTime: number = now(); const edgeTileIDs: Set<OverscaledTileID> = getEdgeTiles(idealTileIDs); for (const idealID of idealTileIDs) { const idealTile = inViewTiles.getTileById(idealID.key); // reset any previously departing(ed) tiles that are now ideal tiles if (idealTile.fadingDirection === FadingDirections.Departing || idealTile.fadeOpacity === 0) { idealTile.resetFadeLogic(); } const parentIsFader = updateFadingAncestor(inViewTiles, idealTile, retain, currentTime, maxFadingAncestorLevels, sourceMinZoom, rasterFadeDuration); if (parentIsFader) continue; const childIsFader = updateFadingDescendents(inViewTiles, idealTile, retain, currentTime, sourceMaxZoom, rasterFadeDuration); if (childIsFader) continue; const edgeIsFader = updateFadingEdge(idealTile, edgeTileIDs, currentTime, rasterFadeDuration); if (edgeIsFader) continue; // for all remaining non-fading ideal tiles reset the fade logic idealTile.resetFadeLogic(); } } /** * Many-to-one cross-fade. Set 4 ideal tiles as the fading base for a rendered parent tile * as the fading parent. Here the parent is fading out and the ideal tile is fading in. * * Parent tile - fading out ■ -- Fading Parent * ┌──────────────┬──────┴───────┬──────────────┐ * Ideal tiles - fading in ■ ■ ■ ■ -- Base Role = Incoming * ┌───┬─┴─┬───┐ ┌───┬─┴─┬───┐ ┌───┬─┴─┬───┐ ┌───┬─┴─┬───┐ * ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ */ function updateFadingAncestor( inViewTiles: InViewTiles, idealTile: Tile, retain: Record<string, OverscaledTileID>, now: number, maxFadingAncestorLevels: number, sourceMinZoom: number, rasterFadeDuration: number): boolean { if (!idealTile.hasData()) return false; const {tileID: idealID, fadingRole, fadingDirection, fadingParentID} = idealTile; // ideal tile already has fading parent - retain and return if (fadingRole === FadingRoles.Base && fadingDirection === FadingDirections.Incoming && fadingParentID) { retain[fadingParentID.key] = fadingParentID; return true; } // find a loaded parent tile to fade with the ideal tile const minAncestorZ = Math.max(idealID.overscaledZ - maxFadingAncestorLevels, sourceMinZoom); for (let ancestorZ = idealID.overscaledZ - 1; ancestorZ >= minAncestorZ; ancestorZ--) { const ancestorID = idealID.scaledTo(ancestorZ); const ancestorTile = inViewTiles.getLoadedTile(ancestorID); if (!ancestorTile) continue; // ideal tile (base) is fading in idealTile.setCrossFadeLogic({ fadingRole: FadingRoles.Base, fadingDirection: FadingDirections.Incoming, fadingParentID: ancestorTile.tileID, // fading out fadeEndTime: now + rasterFadeDuration }); // ancestor tile (parent) is fading out ancestorTile.setCrossFadeLogic({ fadingRole: FadingRoles.Parent, fadingDirection: FadingDirections.Departing, fadeEndTime: now + rasterFadeDuration }); retain[ancestorID.key] = ancestorID; return true; } return false; } /** * Many-to-one cross-fade. Search descendents of ideal tiles as the fading base with the ideal tile * as the fading parent. Here the children are fading out and the ideal tile is fading in. * * ■ * ┌──────────────┬──────┴───────┬──────────────┐ * Ideal tiles - fading in ■ ■ ■ ■ -- Fading Parent * ┌───┬─┴─┬───┐ ┌───┬─┴─┬───┐ ┌───┬─┴─┬───┐ ┌───┬─┴─┬───┐ * Child tiles - fading out ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ -- Base Role = Departing * * Try direct children first. If none found, try grandchildren. Stops at the first generation that provides a fader. */ function updateFadingDescendents(inViewTiles: InViewTiles, idealTile: Tile, retain: Record<string, OverscaledTileID>, now: number, sourceMaxZoom: number, rasterFadeDuration: number): boolean { if (!idealTile.hasData()) return false; // search first level of descendents (4 tiles) const idealChildren = idealTile.tileID.children(sourceMaxZoom); let hasFader = updateFadingChildren(inViewTiles, idealTile, idealChildren, retain, now, sourceMaxZoom, rasterFadeDuration); if (hasFader) return true; // search second level of descendents (16 tiles) for (const childID of idealChildren) { const grandChildIDs = childID.children(sourceMaxZoom); if (updateFadingChildren(inViewTiles, idealTile, grandChildIDs, retain, now, sourceMaxZoom, rasterFadeDuration)) { hasFader = true; } } return hasFader; } function updateFadingChildren( inViewTiles: InViewTiles, idealTile: Tile, childIDs: OverscaledTileID[], retain: Record<string, OverscaledTileID>, now: number, sourceMaxZoom: number, rasterFadeDuration: number): boolean { if (childIDs[0].overscaledZ >= sourceMaxZoom) return false; let foundFader = false; // find loaded child tiles to fade with the ideal tile for (const childID of childIDs) { const childTile = inViewTiles.getLoadedTile(childID); if (!childTile) continue; const {fadingRole, fadingDirection, fadingParentID} = childTile; if (fadingRole !== FadingRoles.Base || fadingDirection !== FadingDirections.Departing || !fadingParentID) { // child tile (base) is fading out childTile.setCrossFadeLogic({ fadingRole: FadingRoles.Base, fadingDirection: FadingDirections.Departing, fadingParentID: idealTile.tileID, fadeEndTime: now + rasterFadeDuration }); // ideal tile (parent) is fading in idealTile.setCrossFadeLogic({ fadingRole: FadingRoles.Parent, fadingDirection: FadingDirections.Incoming, fadeEndTime: now + rasterFadeDuration }); } retain[childID.key] = childID; foundFader = true; } return foundFader; } /** * One-to-one self fading for unloaded edge tiles (for panning sideways on map). for loading tiles over gaps it feels * more natural for them to fade in, however if they are already loaded/cached then there is no need to fade as map will * look cohesive with no gaps. Note that draw_raster determines fade priority, as many-to-one fade supersedes edge fading. */ function updateFadingEdge(idealTile: Tile, edgeTileIDs: Set<OverscaledTileID>, now: number, rasterFadeDuration: number): boolean { const idealID: OverscaledTileID = idealTile.tileID; // tile is already self fading if (idealTile.selfFading) { return true; } // fading not needed for tiles that are already loaded if (idealTile.hasData()) { return false; } // enable fading for loading edges with no data if (edgeTileIDs.has(idealID)) { const fadeEndTime = now + rasterFadeDuration; idealTile.setSelfFadeLogic(fadeEndTime); return true; } return false; } export function hasRasterTransition(inViewTiles: InViewTiles, rasterFadeDuration: number) { if (rasterFadeDuration <= 0) { return false; } const currentTime = now(); for (const tile of inViewTiles.getAllTiles()) { if (tile.fadeEndTime >= currentTime) { return true; } } return false; }