@deck.gl/core
Version:
deck.gl core library
305 lines (265 loc) • 10.6 kB
text/typescript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import {COORDINATE_SYSTEM, PROJECTION_MODE, UNIT} from '../../lib/constants';
// We are generating these from the js code in constants.js
const COORDINATE_SYSTEM_WGSL_CONSTANTS = Object.keys(COORDINATE_SYSTEM)
.map(key => `const COORDINATE_SYSTEM_${key}: i32 = ${COORDINATE_SYSTEM[key]};`)
.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<f32> = vec3<f32>(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<f32>,
projectionMode: i32,
scale: f32,
commonUnitsPerWorldUnit: vec3<f32>,
commonUnitsPerWorldUnit2: vec3<f32>,
center: vec4<f32>,
modelMatrix: mat4x4<f32>,
viewProjectionMatrix: mat4x4<f32>,
viewportSize: vec2<f32>,
devicePixelRatio: f32,
focalDistance: f32,
cameraPosition: vec3<f32>,
coordinateOrigin: vec3<f32>,
commonOrigin: vec3<f32>,
pseudoMeters: i32,
};
@group(0) @binding(0)
var<uniform> project: ProjectUniforms;
// -----------------------------------------------------------------------------
// Geometry data
// (In your GLSL code, "geometry" was assumed to be available globally. In WGSL,
// you might supply this via vertex attributes or a uniform. Here we define a
// uniform struct for demonstration.)
// -----------------------------------------------------------------------------
// Structure to carry additional geometry data used by deck.gl filters.
struct Geometry {
worldPosition: vec3<f32>,
worldPositionAlt: vec3<f32>,
position: vec4<f32>,
uv: vec2<f32>,
pickingColor: vec3<f32>,
};
// @group(0) @binding(1)
var<private> 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<f32>) -> vec2<f32> {
return meters * project.commonUnitsPerMeter.xy * project_size();
}
fn project_size_vec3(meters: vec3<f32>) -> vec3<f32> {
return meters * project.commonUnitsPerMeter * project_size();
}
fn project_size_vec4(meters: vec4<f32>) -> vec4<f32> {
return vec4<f32>(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<f32>) -> mat3x3<f32> {
let uz = normalize(up);
let ux = select(
vec3<f32>(1.0, 0.0, 0.0),
normalize(vec3<f32>(uz.y, -uz.x, 0.0)),
abs(uz.z) == 1.0
);
let uy = cross(uz, ux);
return mat3x3<f32>(ux, uy, uz);
}
// Since WGSL does not support "out" parameters, we return a struct.
struct RotationResult {
needsRotation: bool,
transform: mat3x3<f32>,
};
fn project_needs_rotation(commonPosition: vec3<f32>) -> RotationResult {
if (project.projectionMode == PROJECTION_MODE_GLOBE) {
return RotationResult(true, project_get_orientation_matrix(commonPosition));
} else {
return RotationResult(false, mat3x3<f32>()); // identity alternative if needed
};
}
// Projects a normal vector from the current coordinate system to world space.
fn project_normal(vector: vec3<f32>) -> vec3<f32> {
let normal_modelspace = project.modelMatrix * vec4<f32>(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<f32>) -> vec4<f32> {
let dy: f32 = offset.y;
let commonUnitsPerWorldUnit = project.commonUnitsPerWorldUnit + project.commonUnitsPerWorldUnit2 * dy;
return vec4<f32>(offset.xyz * commonUnitsPerWorldUnit, offset.w);
}
// Projects lng/lat coordinates to a unit tile [0,1]
fn project_mercator_(lnglat: vec2<f32>) -> vec2<f32> {
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<f32>(
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<f32>) -> vec3<f32> {
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<f32>(
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<f32>, position64Low: vec3<f32>) -> vec4<f32> {
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<f32>(
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<f32>(
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<f32>(
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<f32>(position64Low, 0.0));
}
// Overloaded versions for different input types.
fn project_position_vec4_f32(position: vec4<f32>) -> vec4<f32> {
return project_position_vec4_f64(position, ZERO_64_LOW);
}
fn project_position_vec3_f64(position: vec3<f32>, position64Low: vec3<f32>) -> vec3<f32> {
let projected_position = project_position_vec4_f64(vec4<f32>(position, 1.0), position64Low);
return projected_position.xyz;
}
fn project_position_vec3_f32(position: vec3<f32>) -> vec3<f32> {
let projected_position = project_position_vec4_f64(vec4<f32>(position, 1.0), ZERO_64_LOW);
return projected_position.xyz;
}
fn project_position_vec2_f32(position: vec2<f32>) -> vec2<f32> {
let projected_position = project_position_vec4_f64(vec4<f32>(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<f32>, viewProjectionMatrix: mat4x4<f32>, center: vec4<f32>) -> vec4<f32> {
return viewProjectionMatrix * position + center;
}
// Uses the project viewProjectionMatrix and center.
fn project_common_position_to_clipspace(position: vec4<f32>) -> vec4<f32> {
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<f32>) -> vec2<f32> {
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<f32>) -> vec2<f32> {
return pixels / project.scale;
}
`;