// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {PROJECTION_MODE, UNIT} from '../../lib/constants'; import {getShaderCoordinateSystem} from './viewport-uniforms'; const SHADER_COORDINATE_SYSTEMS = [ 'default', 'lnglat', 'meter-offsets', 'lnglat-offsets', 'cartesian' ] as const; const COORDINATE_SYSTEM_WGSL_CONSTANTS = SHADER_COORDINATE_SYSTEMS.map( coordinateSystem => `const COORDINATE_SYSTEM_${coordinateSystem.toUpperCase().replaceAll('-', '_')}: i32 = ${getShaderCoordinateSystem(coordinateSystem)};` ).join(''); const PROJECTION_MODE_WGSL_CONSTANTS = Object.keys(PROJECTION_MODE) .map(key => `const PROJECTION_MODE_${key}: i32 = ${PROJECTION_MODE[key]};`) .join(''); const UNIT_WGSL_CONSTANTS = Object.keys(UNIT) .map(key => `const UNIT_${key.toUpperCase()}: i32 = ${UNIT[key]};`) .join(''); export const projectWGSLHeader = /* wgsl */ `\ ${COORDINATE_SYSTEM_WGSL_CONSTANTS} ${PROJECTION_MODE_WGSL_CONSTANTS} ${UNIT_WGSL_CONSTANTS} const TILE_SIZE: f32 = 512.0; const PI: f32 = 3.1415926536; const WORLD_SCALE: f32 = TILE_SIZE / (PI * 2.0); const ZERO_64_LOW: vec3 = vec3(0.0, 0.0, 0.0); const EARTH_RADIUS: f32 = 6370972.0; // meters const GLOBE_RADIUS: f32 = 256.0; // ----------------------------------------------------------------------------- // Uniform block (converted from GLSL uniform block) // ----------------------------------------------------------------------------- struct ProjectUniforms { wrapLongitude: i32, coordinateSystem: i32, commonUnitsPerMeter: vec3, projectionMode: i32, scale: f32, commonUnitsPerWorldUnit: vec3, commonUnitsPerWorldUnit2: vec3, center: vec4, modelMatrix: mat4x4, viewProjectionMatrix: mat4x4, viewportSize: vec2, devicePixelRatio: f32, focalDistance: f32, cameraPosition: vec3, coordinateOrigin: vec3, commonOrigin: vec3, pseudoMeters: i32, }; @group(0) @binding(auto) var project: ProjectUniforms; // ----------------------------------------------------------------------------- // Geometry data shared across the project helpers. // The active layer shader is responsible for populating this private module // state before calling the project functions below. // ----------------------------------------------------------------------------- // Structure to carry additional geometry data used by deck.gl filters. struct Geometry { worldPosition: vec3, worldPositionAlt: vec3, position: vec4, normal: vec3, uv: vec2, pickingColor: vec3, }; var geometry: Geometry; `; export const projectWGSL = /* wgsl */ `\ ${projectWGSLHeader} // ----------------------------------------------------------------------------- // Functions // ----------------------------------------------------------------------------- // Returns an adjustment factor for commonUnitsPerMeter fn _project_size_at_latitude(lat: f32) -> f32 { let y = clamp(lat, -89.9, 89.9); return 1.0 / cos(radians(y)); } // Overloaded version: scales a value in meters at a given latitude. fn _project_size_at_latitude_m(meters: f32, lat: f32) -> f32 { return meters * project.commonUnitsPerMeter.z * _project_size_at_latitude(lat); } // Computes a non-linear scale factor based on geometry. // (Note: This function relies on "geometry" being provided.) fn project_size() -> f32 { if (project.projectionMode == PROJECTION_MODE_WEB_MERCATOR && project.coordinateSystem == COORDINATE_SYSTEM_LNGLAT && project.pseudoMeters == 0) { if (geometry.position.w == 0.0) { return _project_size_at_latitude(geometry.worldPosition.y); } let y: f32 = geometry.position.y / TILE_SIZE * 2.0 - 1.0; let y2 = y * y; let y4 = y2 * y2; let y6 = y4 * y2; return 1.0 + 4.9348 * y2 + 4.0587 * y4 + 1.5642 * y6; } return 1.0; } // Overloads to scale offsets (meters to world units) fn project_size_float(meters: f32) -> f32 { return meters * project.commonUnitsPerMeter.z * project_size(); } fn project_size_vec2(meters: vec2) -> vec2 { return meters * project.commonUnitsPerMeter.xy * project_size(); } fn project_size_vec3(meters: vec3) -> vec3 { return meters * project.commonUnitsPerMeter * project_size(); } fn project_size_vec4(meters: vec4) -> vec4 { return vec4(meters.xyz * project.commonUnitsPerMeter, meters.w); } // Returns a rotation matrix aligning the z‑axis with the given up vector. fn project_get_orientation_matrix(up: vec3) -> mat3x3 { let uz = normalize(up); let ux = select( vec3(1.0, 0.0, 0.0), normalize(vec3(uz.y, -uz.x, 0.0)), abs(uz.z) == 1.0 ); let uy = cross(uz, ux); return mat3x3(ux, uy, uz); } // Since WGSL does not support "out" parameters, we return a struct. struct RotationResult { needsRotation: bool, transform: mat3x3, }; fn project_needs_rotation(commonPosition: vec3) -> RotationResult { if (project.projectionMode == PROJECTION_MODE_GLOBE) { return RotationResult(true, project_get_orientation_matrix(commonPosition)); } else { return RotationResult(false, mat3x3()); // identity alternative if needed }; } // Projects a normal vector from the current coordinate system to world space. fn project_normal(vector: vec3) -> vec3 { let normal_modelspace = project.modelMatrix * vec4(vector, 0.0); var n = normalize(normal_modelspace.xyz * project.commonUnitsPerMeter); let rotResult = project_needs_rotation(geometry.position.xyz); if (rotResult.needsRotation) { n = rotResult.transform * n; } return n; } // Applies a scale offset based on y-offset (dy) fn project_offset_(offset: vec4) -> vec4 { let dy: f32 = offset.y; let commonUnitsPerWorldUnit = project.commonUnitsPerWorldUnit + project.commonUnitsPerWorldUnit2 * dy; return vec4(offset.xyz * commonUnitsPerWorldUnit, offset.w); } // Projects lng/lat coordinates to a unit tile [0,1] fn project_mercator_(lnglat: vec2) -> vec2 { var x = lnglat.x; if (project.wrapLongitude != 0) { x = ((x + 180.0) % 360.0) - 180.0; } let y = clamp(lnglat.y, -89.9, 89.9); return vec2( radians(x) + PI, PI + log(tan(PI * 0.25 + radians(y) * 0.5)) ) * WORLD_SCALE; } // Projects lng/lat/z coordinates for a globe projection. fn project_globe_(lnglatz: vec3) -> vec3 { let lambda = radians(lnglatz.x); let phi = radians(lnglatz.y); let cosPhi = cos(phi); let D = (lnglatz.z / EARTH_RADIUS + 1.0) * GLOBE_RADIUS; return vec3( sin(lambda) * cosPhi, -cos(lambda) * cosPhi, sin(phi) ) * D; } // Projects positions (with an optional 64-bit low part) from the input // coordinate system to the common space. fn project_position_vec4_f64(position: vec4, position64Low: vec3) -> vec4 { var position_world = project.modelMatrix * position; // Work around for a Mac+NVIDIA bug: if (project.projectionMode == PROJECTION_MODE_WEB_MERCATOR) { if (project.coordinateSystem == COORDINATE_SYSTEM_LNGLAT) { return vec4( project_mercator_(position_world.xy), _project_size_at_latitude_m(position_world.z, position_world.y), position_world.w ); } if (project.coordinateSystem == COORDINATE_SYSTEM_CARTESIAN) { position_world = vec4f(position_world.xyz + project.coordinateOrigin, position_world.w); } } if (project.projectionMode == PROJECTION_MODE_GLOBE) { if (project.coordinateSystem == COORDINATE_SYSTEM_LNGLAT) { return vec4( project_globe_(position_world.xyz), position_world.w ); } } if (project.projectionMode == PROJECTION_MODE_WEB_MERCATOR_AUTO_OFFSET) { if (project.coordinateSystem == COORDINATE_SYSTEM_LNGLAT) { if (abs(position_world.y - project.coordinateOrigin.y) > 0.25) { return vec4( project_mercator_(position_world.xy) - project.commonOrigin.xy, project_size_float(position_world.z), position_world.w ); } } } if (project.projectionMode == PROJECTION_MODE_IDENTITY || (project.projectionMode == PROJECTION_MODE_WEB_MERCATOR_AUTO_OFFSET && (project.coordinateSystem == COORDINATE_SYSTEM_LNGLAT || project.coordinateSystem == COORDINATE_SYSTEM_CARTESIAN))) { position_world = vec4f(position_world.xyz - project.coordinateOrigin, position_world.w); } return project_offset_(position_world) + project_offset_(project.modelMatrix * vec4(position64Low, 0.0)); } // Overloaded versions for different input types. fn project_position_vec4_f32(position: vec4) -> vec4 { return project_position_vec4_f64(position, ZERO_64_LOW); } fn project_position_vec3_f64(position: vec3, position64Low: vec3) -> vec3 { let projected_position = project_position_vec4_f64(vec4(position, 1.0), position64Low); return projected_position.xyz; } fn project_position_vec3_f32(position: vec3) -> vec3 { let projected_position = project_position_vec4_f64(vec4(position, 1.0), ZERO_64_LOW); return projected_position.xyz; } fn project_position_vec2_f32(position: vec2) -> vec2 { let projected_position = project_position_vec4_f64(vec4(position, 0.0, 1.0), ZERO_64_LOW); return projected_position.xy; } // Transforms a common space position to clip space. fn project_common_position_to_clipspace_with_projection(position: vec4, viewProjectionMatrix: mat4x4, center: vec4) -> vec4 { return viewProjectionMatrix * position + center; } // Uses the project viewProjectionMatrix and center. fn project_common_position_to_clipspace(position: vec4) -> vec4 { return project_common_position_to_clipspace_with_projection(position, project.viewProjectionMatrix, project.center); } // Returns a clip space offset corresponding to a given number of screen pixels. fn project_pixel_size_to_clipspace(pixels: vec2) -> vec2 { let offset = pixels / project.viewportSize * project.devicePixelRatio * 2.0; return offset * project.focalDistance; } fn project_meter_size_to_pixel(meters: f32) -> f32 { return project_size_float(meters) * project.scale; } fn project_unit_size_to_pixel(size: f32, unit: i32) -> f32 { if (unit == UNIT_METERS) { return project_meter_size_to_pixel(size); } else if (unit == UNIT_COMMON) { return size * project.scale; } // UNIT_PIXELS: no scaling applied. return size; } fn project_pixel_size_float(pixels: f32) -> f32 { return pixels / project.scale; } fn project_pixel_size_vec2(pixels: vec2) -> vec2 { return pixels / project.scale; } `;