latkit 0.3.0

Columnar data drawn as vertices, edges, lines, and areas on the GPU.
Documentation
// The backdrop under every mark: a plane's or diagram's grid, or the earth's ground and graticule,
// lit by daylight, with the atmosphere around the globe. Lines are one pixel wide and antialiased
// per axis. Their spacing steps by powers of ten, and the next coarser spacing fades in as lines
// get dense, so no seam shows. Lines fade where the view grazes the ground, and on a plane, with
// distance from where the camera looks. The round globe writes its depth slightly below the ground,
// so marks behind it are hidden and marks on it still draw.

import package::lib::types::{SPACE_EARTH, SPACE_PLANE, SPACE_DIAGRAM, GRID_OFF, GRID_DOTS};
import package::lib::bind::{frame, eye};
import package::lib::color::output;
import package::lib::project::{PI, NEAR_Z, FAR_Z, globe_center};
import package::lib::daylight::daylight;

struct Varyings {
    @builtin(position) clip: vec4f,
    @location(0) ndc: vec2f,
}

// How far below the ground the globe's depth is written, in globe radii. It exceeds the sag of a
// chord a few degrees long, so paths and areas on the ground draw over the globe.
const GROUND_BIAS: f32 = 1e-3;

// A backdrop pixel: its color, and the ground's depth there.
struct Ground {
    color: vec4f,
    depth: f32,
}

struct Out {
    @location(0) color: vec4f,
    @builtin(frag_depth) depth: f32,
}

@vertex
fn vertex(@builtin(vertex_index) i: u32) -> Varyings {
    let uv = vec2f(f32((i << 1u) & 2u), f32(i & 2u));
    let ndc = uv * 2.0 - 1.0;
    return Varyings(vec4f(ndc, 0.0, 1.0), ndc);
}

// Coverage of grid lines `pitch` apart at `p`, where `p` changes by `w` per pixel. Lines are a
// pixel wide and fade out as their cells shrink to a few pixels.
fn lines(p: vec2f, w: vec2f, pitch: f32) -> f32 {
    let cells = max(w / pitch, vec2f(1e-12));
    let px = abs(fract(p / pitch + 0.5) - 0.5) / cells;
    let c = saturate(1.0 - px) * (1.0 - smoothstep(vec2f(0.08), vec2f(0.25), cells));
    return max(c.x, c.y);
}

// Coverage of grid dots `pitch` apart at `p`, where `p` changes by `w` per pixel. Dots are a CSS
// pixel across on any screen and fade out as their cells shrink to a few pixels.
fn dots(p: vec2f, w: vec2f, pitch: f32) -> f32 {
    let cells = max(w / pitch, vec2f(1e-12));
    let px = abs(fract(p / pitch + 0.5) - 0.5) / cells;
    return saturate(eye.pixel_ratio + 0.5 - length(px)) * (1.0 - smoothstep(0.08, 0.25, max(cells.x, cells.y)));
}

// Grid lines spaced `pitch` times a power of ten, with the power between `lowest` and `highest`.
// Lines draw at the spacing just under 20 pixels, and the next coarser spacing fades in as that one
// gets denser.
fn grid(p: vec2f, w: vec2f, pitch: f32, lowest: f32, highest: f32, dotted: bool) -> f32 {
    let level = clamp(log2(max(max(w.x, w.y) * 20.0 / pitch, 1e-30)) * 0.30103, lowest, highest);
    let k = floor(level);
    let t = level - k;
    let step = pitch * exp2(k * 3.3219281);
    if dotted {
        return max(dots(p, w, step), dots(p, w, step * 10.0) * t);
    }
    return max(lines(p, w, step), lines(p, w, step * 10.0) * t);
}

fn blend(color: vec4f, over: vec4f, coverage: f32) -> vec4f {
    return vec4f(mix(color.rgb, over.rgb, coverage * over.a), color.a);
}

// The plane's grid at `g`, where the view ray meets elevation 0, faded where the ray grazes the
// plane and away from the center.
fn plane(g: vec3f, d: vec3f, ahead: bool) -> vec4f {
    let p = g.xy + frame.center_hi + frame.center_lo;
    let w = fwidth(g.xy);
    let ground = frame.background;
    if frame.grid_kind == GRID_OFF {
        return ground;
    }
    let near = 1.0 - smoothstep(0.6, 1.0, length(g.xy) / max(frame.grid_fade, 1e-30));
    let fade = f32(ahead) * smoothstep(0.02, 0.15, abs(d.z)) * select(1.0, near, frame.grid_fade > 0.0);
    let c = grid(p, w, frame.grid_pitch, 0.0, 30.0, frame.grid_kind == GRID_DOTS) * fade;
    return blend(ground, frame.grid_color, c);
}

// Longitude and latitude in degrees at world point `w` on the flat map.
fn map_degrees(w: vec3f) -> vec2f {
    let lat = degrees(2.0 * atan(exp(w.y + frame.mercator_y)) - 0.5 * PI);
    return vec2f(frame.center_hi.x + frame.center_lo.x + degrees(w.x), lat);
}

// Longitude and latitude in degrees at world point `w` on the globe of `radius`.
fn globe_degrees(w: vec3f, radius: f32) -> vec2f {
    let lat0 = radians(frame.center_hi.y + frame.center_lo.y);
    let u = normalize(vec3f(w.xy, w.z + radius));
    let v = vec3f(u.x, u.y * cos(lat0) + u.z * sin(lat0), -u.y * sin(lat0) + u.z * cos(lat0));
    let lon = frame.center_hi.x + frame.center_lo.x + degrees(atan2(v.x, v.z));
    return vec2f(lon, degrees(asin(clamp(v.y, -1.0, 1.0))));
}

// The earth: the flat map's square or the globe's disc, blended as the projection morphs, lit by
// daylight, with the graticule on it and the atmosphere around the globe.
fn earth(o: vec3f, d: vec3f, g: vec3f, ahead: bool) -> Ground {
    // The flat map is one world. It ends where Web Mercator does, at y = ±π, and at the
    // antimeridian, x = ±π from the prime meridian. The distance to the antimeridian starts from
    // the center's longitude part by part, so the edge holds still at any zoom.
    let band = PI - abs(g.y + frame.mercator_y);
    let east = radians(180.0 - frame.center_hi.x - frame.center_lo.x) - g.x;
    let west = radians(180.0 + frame.center_hi.x + frame.center_lo.x) + g.x;
    let side = min(east, west);
    let across = saturate(side / max(fwidth(side), 1e-9) + 0.5);
    let flat = saturate(band / max(fwidth(band), 1e-9) + 0.5) * across * f32(ahead);
    let center = globe_center();
    let radius = -center.z;
    let to_center = center - o;
    let along = dot(to_center, d);
    let off = length(to_center - d * along);
    let miss = off - radius;
    let round = saturate(-miss / max(fwidth(miss), 1e-9) + 0.5) * f32(along > 0.0);
    let covered = mix(flat, round, frame.globe);
    let hit = o + d * (along - sqrt(max(radius * radius - off * off, 0.0)));
    let globe = frame.globe >= 0.5;
    var p: vec2f;
    var ground = frame.ground;
    if globe {
        p = globe_degrees(hit, radius);
    } else {
        p = map_degrees(g);
    }
    ground = vec4f(ground.rgb * daylight(p), ground.a);
    var color = blend(frame.background, ground, covered);
    let facing = dot(normalize(hit - center), -d);
    if frame.graticule != 0u {
        // Longitude jumps where it wraps. A second, shifted wrapping keeps the derivative steady
        // there.
        let wrapped = fract(p.x / 360.0) * 360.0;
        let w = vec2f(min(fwidth(p.x), fwidth(wrapped)), fwidth(p.y));
        let fade = select(smoothstep(0.02, 0.15, abs(d.z)), smoothstep(0.0, 0.3, facing), globe);
        color = blend(color, frame.grid_color, grid(p, w, 1.0, -2.0, 1.0, false) * fade * covered);
    }
    if frame.atmosphere.a > 0.0 && frame.globe > 0.0 {
        // A thin rim of atmosphere over the globe's edge, and a halo out to 1.045 globe radii. It
        // is lit where the sun is up over the ground nearest the view ray.
        let rim = pow(1.0 - saturate(facing), 6.0) * round;
        let halo = pow(1.0 - smoothstep(radius, radius * 1.045, off), 2.0) * (1.0 - round) * f32(along > 0.0);
        let below = daylight(globe_degrees(o + d * along, radius));
        let air = (rim + halo) * frame.atmosphere.a * frame.globe * mix(0.2, 1.0, below);
        color = vec4f(mix(color.rgb, frame.atmosphere.rgb, saturate(air)), color.a);
    }
    var depth = FAR_Z;
    if frame.globe >= 1.0 && round > 0.5 {
        let under = eye.clip_from_world * vec4f(hit - normalize(hit - center) * radius * GROUND_BIAS, 1.0);
        depth = clamp(under.z / under.w, FAR_Z, NEAR_Z);
    }
    return Ground(color, depth);
}

@fragment
fn fragment(v: Varyings) -> Out {
    let near = eye.world_from_clip * vec4f(v.ndc, NEAR_Z, 1.0);
    let far = eye.world_from_clip * vec4f(v.ndc, FAR_Z, 1.0);
    let o = near.xyz / near.w;
    let d = normalize(far.xyz / far.w - o);
    // Where the ray meets elevation 0.
    let t = -o.z / d.z;
    let g = o + d * max(t, 0.0);
    var color = frame.background;
    var depth = FAR_Z;
    if frame.space == SPACE_PLANE || frame.space == SPACE_DIAGRAM {
        color = plane(g, d, t > 0.0);
    } else if frame.space == SPACE_EARTH {
        let ground = earth(o, d, g, t > 0.0);
        color = ground.color;
        depth = ground.depth;
    }
    return Out(output(vec4f(color.rgb * color.a, color.a)), depth);
}