UNPKG

maplibre-gl

Version:

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

163 lines (148 loc) • 6.3 kB
import {mat4} from 'gl-matrix'; import { Uniform1i, Uniform1f, Uniform2f, UniformColor, UniformFloatArray, UniformColorArray, UniformMatrix4f, Uniform4f } from '../uniform_binding.ts'; import {EXTENT} from '../../data/extent.ts'; import type {Context} from '../../webgl/context.ts'; import type {UniformValues, UniformLocations} from '../uniform_binding.ts'; import type {Tile} from '../../tile/tile.ts'; import type {IReadonlyTransform} from '../../geo/transform_interface.ts'; import type {HillshadeStyleLayer} from '../../style/style_layer/hillshade_style_layer.ts'; import type {DEMData} from '../../data/dem_data.ts'; import type {OverscaledTileID} from '../../tile/tile_id.ts'; export type HillshadeUniformsType = { 'u_image': Uniform1i; 'u_latrange': Uniform2f; 'u_exaggeration': Uniform1f; 'u_altitudes': UniformFloatArray; 'u_azimuths': UniformFloatArray; 'u_accent': UniformColor; 'u_method': Uniform1i; 'u_shadows': UniformColorArray; 'u_highlights': UniformColorArray; }; export type HillshadePrepareUniformsType = { 'u_matrix': UniformMatrix4f; 'u_image': Uniform1i; 'u_dimension': Uniform2f; 'u_zoom': Uniform1f; 'u_unpack': Uniform4f; 'u_log2_world_width': Uniform1f; }; const hillshadeUniforms = (context: Context, locations: UniformLocations): HillshadeUniformsType => ({ 'u_image': new Uniform1i(context, locations.u_image), 'u_latrange': new Uniform2f(context, locations.u_latrange), 'u_exaggeration': new Uniform1f(context, locations.u_exaggeration), 'u_altitudes': new UniformFloatArray(context, locations.u_altitudes), 'u_azimuths': new UniformFloatArray(context, locations.u_azimuths), 'u_accent': new UniformColor(context, locations.u_accent), 'u_method': new Uniform1i(context, locations.u_method), 'u_shadows': new UniformColorArray(context, locations.u_shadows), 'u_highlights': new UniformColorArray(context, locations.u_highlights) }); const hillshadePrepareUniforms = (context: Context, locations: UniformLocations): HillshadePrepareUniformsType => ({ 'u_matrix': new UniformMatrix4f(context, locations.u_matrix), 'u_image': new Uniform1i(context, locations.u_image), 'u_dimension': new Uniform2f(context, locations.u_dimension), 'u_zoom': new Uniform1f(context, locations.u_zoom), 'u_unpack': new Uniform4f(context, locations.u_unpack), 'u_log2_world_width': new Uniform1f(context, locations.u_log2_world_width) }); const hillshadeUniformValues = ( transform: IReadonlyTransform, tile: Tile, layer: HillshadeStyleLayer, ): UniformValues<HillshadeUniformsType> => { const accent = layer.paint.get('hillshade-accent-color'); let method; switch (layer.paint.get('hillshade-method')) { case 'basic': method = 4; break; case 'combined': method = 1; break; case 'igor': method = 2; break; case 'multidirectional': method = 3; break; case 'standard': default: method = 0; break; } const illumination = layer.getIlluminationProperties(); for (let i = 0; i < illumination.directionRadians.length; i++) { // modify azimuthal angle by map rotation if light is anchored at the viewport if (layer.paint.get('hillshade-illumination-anchor') === 'viewport') { illumination.directionRadians[i] += transform.bearingInRadians; } } return { 'u_image': 0, 'u_latrange': getTileLatRange(transform, tile.tileID), 'u_exaggeration': layer.paint.get('hillshade-exaggeration'), 'u_altitudes': illumination.altitudeRadians, 'u_azimuths': illumination.directionRadians, 'u_accent': accent, 'u_method': method, 'u_highlights': illumination.highlightColor, 'u_shadows': illumination.shadowColor }; }; const hillshadeUniformPrepareValues = (transform: IReadonlyTransform, tileID: OverscaledTileID, dem: DEMData): UniformValues<HillshadePrepareUniformsType> => { const stride = dem.stride; const matrix = mat4.create(); // Flip rendering at y axis. mat4.ortho(matrix, 0, EXTENT, -EXTENT, 0, 0, 1); mat4.translate(matrix, matrix, [0, -EXTENT, 0]); return { 'u_matrix': matrix, 'u_image': 1, 'u_dimension': [stride, stride], 'u_zoom': tileID.overscaledZ, 'u_unpack': dem.getUnpackVector(), 'u_log2_world_width': getLog2WorldWidth(transform) }; }; /** * The log2 of the zoom 0 world's width in meters, from which the prepare shader gets the meters per DEM pixel that * it divides elevation differences by. A registered CRS's world is its zoom 0 extent wide, in CRS units taken as * meters. Mercator keeps 25.2562, which with the shader's 3 for the 8 in its slope kernel is the 28.2562 the shader * used before, so its hillshade does not change. */ function getLog2WorldWidth(transform: IReadonlyTransform): number { const worldCoordinateHelper = transform.worldCoordinateHelper; return worldCoordinateHelper.wraps ? 25.2562 : Math.log2(worldCoordinateHelper.metersPerWorldUnit(0.5, 0.5)); } /** * The latitudes of the tile's top and bottom edges, which the hillshade shader uses to correct the mercator * scale distortion of slopes (it scales by `cos(lat)`). Only the wrapping, cylindrical mercator plane stretches * with latitude; any other plane is rendered in its own units and gets `[0, 0]`: `cos(0)` is 1 and the shader * applies no correction. */ function getTileLatRange(transform: IReadonlyTransform, tileID: OverscaledTileID): [number, number] { const worldCoordinateHelper = transform.worldCoordinateHelper; if (!worldCoordinateHelper.wraps) return [0, 0]; // for scaling the magnitude of a points slope by its latitude const tilesAtZoom = Math.pow(2, tileID.canonical.z); const y = tileID.canonical.y; return [ worldCoordinateHelper.lngLatFromWorld(0, y / tilesAtZoom).lat, worldCoordinateHelper.lngLatFromWorld(0, (y + 1) / tilesAtZoom).lat]; } export { hillshadeUniforms, hillshadePrepareUniforms, hillshadeUniformValues, hillshadeUniformPrepareValues };