BREP_render 0.1.0

BREP Rust rendering engine: kernel-fed scene store + wgpu renderer (headless artifact, desktop window, and wasm canvas shells).
Documentation
// brep-render core shaders: shaded faces + screen-space-width edge overlay +
// screen-constant vertex points, all style-driven (R14 material variants).
//
// Face lighting is a linear-space port of the retired artifact page's rig
// (hemisphere 0xbfd4ff/0x202028 @0.9 + key directional @1.4 + fill @0.45,
// Phong shininess 24), view-stable (R15): light directions live in world space
// but the look was tuned against the same rig the retired viewer used. The render target is
// NON-sRGB; fragment shaders encode to sRGB explicitly so the background clear
// (sRGB bytes) needs no conversion.

struct Globals {
    view_proj: mat4x4<f32>,
    // Viewport size in physical pixels (xy), device pixel ratio (z); w unused.
    viewport: vec4<f32>,
    // World-space view direction, camera -> scene (xyz); w unused.
    forward: vec4<f32>,
};

// One style = one material variant (base/selected/hover per kind).
//   faces:  color in LINEAR space (converted f64-exactly at upload, keeping
//           artifact bytes stable) + params.x = flat-shading flag (>0.5)
//   edges:  color in sRGB (written to the non-sRGB target as-is) + alpha;
//           params.x = width in CSS px, params.y = depth nudge
//   points: color in sRGB + alpha; params.x = diameter in CSS px
struct Style {
    color: vec4<f32>,
    params: vec4<f32>,
};

@group(0) @binding(0) var<uniform> globals: Globals;
@group(1) @binding(0) var<uniform> style: Style;

fn srgb_encode(c: vec3<f32>) -> vec3<f32> {
    let lo = c * 12.92;
    let hi = 1.055 * pow(max(c, vec3<f32>(0.0)), vec3<f32>(1.0 / 2.4)) - 0.055;
    return select(hi, lo, c <= vec3<f32>(0.0031308));
}

// ---------------------------------------------------------------------------
// Shaded faces
// ---------------------------------------------------------------------------

struct MeshOut {
    @builtin(position) position: vec4<f32>,
    @location(0) normal: vec3<f32>,
    @location(1) world: vec3<f32>,
};

@vertex
fn vs_mesh(
    @location(0) position: vec3<f32>,
    @location(1) normal: vec3<f32>,
) -> MeshOut {
    var out: MeshOut;
    out.position = globals.view_proj * vec4<f32>(position, 1.0);
    out.normal = normal;
    out.world = position;
    return out;
}

@fragment
fn fs_mesh(in: MeshOut) -> @location(0) vec4<f32> {
    var n = normalize(in.normal);
    if (style.params.x > 0.5) {
        // Flat shading: face normal from screen-space derivatives.
        n = normalize(cross(dpdx(in.world), dpdy(in.world)));
    }
    // Double-sided: flip the normal toward the camera.
    let v = -globals.forward.xyz;
    if (dot(n, v) < 0.0) {
        n = -n;
    }

    // Hemisphere light (up +Y): sky 0xbfd4ff, ground 0x202028,
    // intensity 0.9 (colors pre-converted to linear here).
    let sky = vec3<f32>(0.5209, 0.6584, 1.0);
    let ground = vec3<f32>(0.0144, 0.0144, 0.0212);
    let hemi = mix(ground, sky, n.y * 0.5 + 0.5) * 0.9;

    // Key + fill directionals (white).
    let l_key = normalize(vec3<f32>(1.0, -1.2, 1.5));
    let l_fill = normalize(vec3<f32>(-0.6, 0.7, 0.3));
    let diffuse_light = hemi
        + vec3<f32>(1.4) * max(dot(n, l_key), 0.0)
        + vec3<f32>(0.45) * max(dot(n, l_fill), 0.0);

    // Phong specular, key light only (MeshPhongMaterial specular 0x111111,
    // shininess 24; 0x11/255 -> linear ~0.0056).
    let refl = reflect(-l_key, n);
    let spec = vec3<f32>(0.0056) * pow(max(dot(refl, v), 0.0), 24.0) * 1.4;

    let color = style.color.rgb * diffuse_light + spec;
    return vec4<f32>(srgb_encode(color), 1.0);
}

// Wireframe: the tessellated triangle mesh drawn as lines. Flat base color (no
// lighting), lifted a touch so the mesh reads clearly against the background.
@fragment
fn fs_wire(in: MeshOut) -> @location(0) vec4<f32> {
    return vec4<f32>(srgb_encode(style.color.rgb * 1.15 + vec3<f32>(0.03)), 1.0);
}

// ---------------------------------------------------------------------------
// Edge overlay: screen-constant-width lines (R16). Each SEGMENT is one
// instance (p0, p1); six vertices expand it to a screen-aligned quad, with the
// endpoints extended by the half-width for join coverage.
// ---------------------------------------------------------------------------

struct EdgeOut {
    @builtin(position) position: vec4<f32>,
};

@vertex
fn vs_edge(
    @builtin(vertex_index) vertex_index: u32,
    @location(0) p0: vec3<f32>,
    @location(1) p1: vec3<f32>,
) -> EdgeOut {
    // (end, side) per corner; two CCW triangles of the quad.
    var corners = array<vec2<f32>, 6>(
        vec2<f32>(0.0, -1.0), vec2<f32>(1.0, -1.0), vec2<f32>(0.0, 1.0),
        vec2<f32>(0.0, 1.0), vec2<f32>(1.0, -1.0), vec2<f32>(1.0, 1.0),
    );
    let corner = corners[vertex_index];

    let clip0 = globals.view_proj * vec4<f32>(p0, 1.0);
    let clip1 = globals.view_proj * vec4<f32>(p1, 1.0);
    let half_vp = globals.viewport.xy * 0.5;
    let px0 = clip0.xy / clip0.w * half_vp;
    let px1 = clip1.xy / clip1.w * half_vp;
    var dir = px1 - px0;
    let len = length(dir);
    if (len < 1e-6) {
        dir = vec2<f32>(1.0, 0.0);
    } else {
        dir = dir / len;
    }
    let perp = vec2<f32>(-dir.y, dir.x);
    // Width is in CSS px; scale by the DPR into physical pixels.
    let half_w = style.params.x * max(globals.viewport.z, 1e-3) * 0.5;

    var clip = clip0;
    var px = px0;
    var along = -dir; // extend the start cap backward
    if (corner.x > 0.5) {
        clip = clip1;
        px = px1;
        along = dir;
    }
    let offset_px = perp * (corner.y * half_w) + along * half_w;
    let ndc_offset = offset_px / half_vp * clip.w;

    // NDC depth nudge toward the camera so boundary edges win the z-fight with
    // their own faces (faces also carry a depth bias pushing them back).
    let nudge = style.params.y;
    var out: EdgeOut;
    out.position = vec4<f32>(clip.xy + ndc_offset, clip.z - nudge * clip.w, clip.w);
    return out;
}

@fragment
fn fs_edge() -> @location(0) vec4<f32> {
    return style.color;
}

// ---------------------------------------------------------------------------
// Vertex points: screen-constant-size round sprites (R16), one instance per
// topology vertex.
// ---------------------------------------------------------------------------

struct PointOut {
    @builtin(position) position: vec4<f32>,
    @location(0) local: vec2<f32>,
};

@vertex
fn vs_point(
    @builtin(vertex_index) vertex_index: u32,
    @location(0) center: vec3<f32>,
) -> PointOut {
    var corners = array<vec2<f32>, 6>(
        vec2<f32>(-1.0, -1.0), vec2<f32>(1.0, -1.0), vec2<f32>(-1.0, 1.0),
        vec2<f32>(-1.0, 1.0), vec2<f32>(1.0, -1.0), vec2<f32>(1.0, 1.0),
    );
    let corner = corners[vertex_index];
    let clip = globals.view_proj * vec4<f32>(center, 1.0);
    let half_vp = globals.viewport.xy * 0.5;
    let radius_px = style.params.x * max(globals.viewport.z, 1e-3) * 0.5;
    let ndc_offset = corner * radius_px / half_vp * clip.w;

    var out: PointOut;
    // Nudge points toward the camera so they sit on top of edges/faces.
    out.position = vec4<f32>(clip.xy + ndc_offset, clip.z - 3e-4 * clip.w, clip.w);
    out.local = corner;
    return out;
}

@fragment
fn fs_point(in: PointOut) -> @location(0) vec4<f32> {
    let r = length(in.local);
    // Round sprite with a ~1px anti-aliased rim.
    let radius_px = style.params.x * max(globals.viewport.z, 1e-3) * 0.5;
    let aa = clamp(1.0 - (r - 1.0) * radius_px, 0.0, 1.0);
    if (aa <= 0.0) {
        discard;
    }
    return vec4<f32>(style.color.rgb, style.color.a * aa);
}

// ---------------------------------------------------------------------------
// Overlay widgets: the brep-gizmos `Overlay` — per-vertex-colored
// triangles + per-instance-colored screen-constant-width lines — drawn over the
// solids in their own depth-cleared pass (transform gizmo, ViewCube, datum /
// dimension / curve visuals). Colors are treated as display (sRGB) values and
// written to the non-sRGB target directly (matching the edge overlay), with a
// cheap sRGB-space shade on triangles so faces read with depth. `style.params.x`
// carries the overlay line width in CSS px (fed per pass).
// ---------------------------------------------------------------------------

struct OverlayTriOut {
    @builtin(position) position: vec4<f32>,
    @location(0) normal: vec3<f32>,
    @location(1) color: vec4<f32>,
};

@vertex
fn vs_overlay_tri(
    @location(0) position: vec3<f32>,
    @location(1) normal: vec3<f32>,
    @location(2) color: vec4<f32>,
) -> OverlayTriOut {
    var out: OverlayTriOut;
    out.position = globals.view_proj * vec4<f32>(position, 1.0);
    out.normal = normal;
    out.color = color;
    return out;
}

@fragment
fn fs_overlay_tri(in: OverlayTriOut) -> @location(0) vec4<f32> {
    // Soft two-sided sRGB-space shade so widget faces read without swimming;
    // preserves the vertex color at full light so lines and tris match.
    var n = normalize(in.normal);
    let v = -globals.forward.xyz;
    if (dot(n, v) < 0.0) {
        n = -n;
    }
    let key = normalize(vec3<f32>(0.4, -0.6, 0.8));
    let shade = 0.62 + 0.38 * max(dot(n, key), 0.0);
    return vec4<f32>(in.color.rgb * shade, in.color.a);
}

struct OverlayLineOut {
    @builtin(position) position: vec4<f32>,
    @location(0) color: vec4<f32>,
};

@vertex
fn vs_overlay_line(
    @builtin(vertex_index) vertex_index: u32,
    @location(0) p0: vec3<f32>,
    @location(1) p1: vec3<f32>,
    @location(2) color: vec4<f32>,
) -> OverlayLineOut {
    var corners = array<vec2<f32>, 6>(
        vec2<f32>(0.0, -1.0), vec2<f32>(1.0, -1.0), vec2<f32>(0.0, 1.0),
        vec2<f32>(0.0, 1.0), vec2<f32>(1.0, -1.0), vec2<f32>(1.0, 1.0),
    );
    let corner = corners[vertex_index];

    let clip0 = globals.view_proj * vec4<f32>(p0, 1.0);
    let clip1 = globals.view_proj * vec4<f32>(p1, 1.0);
    let half_vp = globals.viewport.xy * 0.5;
    let px0 = clip0.xy / clip0.w * half_vp;
    let px1 = clip1.xy / clip1.w * half_vp;
    var dir = px1 - px0;
    let len = length(dir);
    if (len < 1e-6) {
        dir = vec2<f32>(1.0, 0.0);
    } else {
        dir = dir / len;
    }
    let perp = vec2<f32>(-dir.y, dir.x);
    let half_w = style.params.x * max(globals.viewport.z, 1e-3) * 0.5;

    var clip = clip0;
    var along = -dir;
    if (corner.x > 0.5) {
        clip = clip1;
        along = dir;
    }
    let offset_px = perp * (corner.y * half_w) + along * half_w;
    let ndc_offset = offset_px / half_vp * clip.w;

    // Small nudge toward the camera so borders read on top of their own fills.
    var out: OverlayLineOut;
    out.position = vec4<f32>(clip.xy + ndc_offset, clip.z - 1.0e-4 * clip.w, clip.w);
    out.color = color;
    return out;
}

@fragment
fn fs_overlay_line(in: OverlayLineOut) -> @location(0) vec4<f32> {
    return in.color;
}