ruviz 0.6.0

High-performance 2D plotting library for Rust
Documentation
struct Camera3D {
    view_projection: mat4x4<f32>,
    axis_aspect: vec4<f32>,
    viewport: vec4<f32>,
};

struct LineMaterial3D {
    color: vec4<f32>,
    parameters: vec4<f32>,
    dash0: vec4<f32>,
    dash1: vec4<f32>,
};

@group(0) @binding(0)
var<uniform> camera: Camera3D;

@group(1) @binding(0)
var<uniform> material: LineMaterial3D;

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) @interpolate(linear) segment_coordinate: vec2<f32>,
    @location(1) @interpolate(flat) segment_length: f32,
};

/// Smallest homogeneous divisor a perspective divide may use.
const W_EPSILON: f32 = 1.0e-6;

/// In front of the near plane (z < 0), so the primitive is discarded whole.
const DEGENERATE_POSITION: vec4<f32> = vec4<f32>(0.0, 0.0, -1.0, 1.0);

@vertex
fn vs_main(
    @builtin(vertex_index) vertex_index: u32,
    @location(0) start: vec4<f32>,
    @location(1) end: vec4<f32>,
) -> VertexOutput {
    let start_clip = camera.view_projection * vec4<f32>(
        start.xyz * camera.axis_aspect.xyz,
        1.0,
    );
    let end_clip = camera.view_projection * vec4<f32>(
        end.xyz * camera.axis_aspect.xyz,
        1.0,
    );
    // Either endpoint at or behind the eye makes the perspective divide below
    // meaningless: a negative `w` mirrors the segment and smears the expanded
    // quad across the whole frame. Emit a degenerate, fully clipped quad
    // instead. The CPU rasterizer reaches the same conclusion by clipping the
    // segment against the near plane before it projects.
    if !(start_clip.w > W_EPSILON && end_clip.w > W_EPSILON) {
        var clipped: VertexOutput;
        clipped.clip_position = DEGENERATE_POSITION;
        clipped.segment_coordinate = vec2<f32>(0.0, 0.0);
        clipped.segment_length = 0.0;
        return clipped;
    }
    let start_ndc = start_clip.xy / start_clip.w;
    let end_ndc = end_clip.xy / end_clip.w;
    let half_viewport = camera.viewport.xy * 0.5;
    let delta_pixels = (end_ndc - start_ndc) * half_viewport;
    let segment_length = length(delta_pixels);
    var direction = vec2<f32>(1.0, 0.0);
    if segment_length > 1.0e-6 {
        direction = delta_pixels / segment_length;
    }
    let perpendicular = vec2<f32>(-direction.y, direction.x);
    let endpoint = vertex_index >= 2u;
    let side = select(-1.0, 1.0, (vertex_index & 1u) == 1u);
    let line_width = max(material.parameters.x * camera.viewport.z / 72.0, 1.0);
    let radius = line_width * 0.5;
    let endpoint_clip = select(start_clip, end_clip, endpoint);
    let endpoint_ndc = select(start_ndc, end_ndc, endpoint);
    let along_offset = select(-radius, radius, endpoint);
    let pixel_offset = direction * along_offset + perpendicular * side * radius;
    let ndc_offset = pixel_offset / half_viewport;

    var output: VertexOutput;
    let clip_xy = (endpoint_ndc + ndc_offset) * endpoint_clip.w;
    output.clip_position = vec4<f32>(clip_xy, endpoint_clip.z, endpoint_clip.w);
    output.segment_coordinate = vec2<f32>(
        select(-radius, segment_length + radius, endpoint),
        side * radius,
    );
    output.segment_length = segment_length;
    return output;
}

fn dash_value(index: u32) -> f32 {
    var value = material.dash1.w;
    if index == 0u { value = material.dash0.x; }
    if index == 1u { value = material.dash0.y; }
    if index == 2u { value = material.dash0.z; }
    if index == 3u { value = material.dash0.w; }
    if index == 4u { value = material.dash1.x; }
    if index == 5u { value = material.dash1.y; }
    if index == 6u { value = material.dash1.z; }
    return max(value * camera.viewport.z / 100.0, 0.25);
}

fn dash_is_on(distance: f32) -> bool {
    let dash_count = u32(round(material.parameters.y));
    let total = material.parameters.z * camera.viewport.z / 100.0;
    if dash_count == 0u || total <= 1.0e-6 {
        return true;
    }
    var position = distance - floor(distance / total) * total;
    for (var index = 0u; index < 8u; index += 1u) {
        if index >= dash_count {
            break;
        }
        let dash_length = dash_value(index);
        if position <= dash_length {
            return (index & 1u) == 0u;
        }
        position -= dash_length;
    }
    return true;
}

@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    let radius = max(material.parameters.x * camera.viewport.z / 72.0, 1.0) * 0.5;
    let along = clamp(input.segment_coordinate.x, 0.0, input.segment_length);
    let cap_distance = input.segment_coordinate.x - along;
    if length(vec2<f32>(cap_distance, input.segment_coordinate.y)) > radius {
        discard;
    }
    if !dash_is_on(along) {
        discard;
    }
    // The scene pipelines blend and resolve premultiplied.
    return vec4<f32>(material.color.rgb * material.color.a, material.color.a);
}