maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
163 lines (148 loc) • 6.3 kB
text/typescript
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
};