bezel-agent 0.1.0

Agent-related UI — thinking orbs for working state and deterministic blob avatars for identity
Documentation
//! Braid: three strands plait around the sphere — the "weaving" state.

use crate::orbs::engine::{
    core::{Dot, Frame, frac, make_proj, radius_scale, with_fib_dirs},
    profiles::{MAX_GHOST_N, MAX_STRAND_N, ModeOpts, count_usize},
};
use std::f32::consts::PI;

pub fn draw_braid_into(size: f32, t: f32, o: &ModeOpts, out: &mut Frame) {
    let cx = size / 2.0;
    let cy = size / 2.0;
    let r = (size / 2.0) * 0.76;
    let pt = make_proj(t * 0.4, 0.3, cx, cy, 1.0);
    let rs = radius_scale(size, o.rs_pow.unwrap_or(0.6));

    let dots = &mut out.dots;
    let ghost_n = count_usize(o.ghost_n, 150.0, 0, MAX_GHOST_N as usize);
    let strand_n = count_usize(o.strand_n, 52.0, 0, MAX_STRAND_N as usize);
    let inv_r = 1.0 / r;
    dots.reserve(ghost_n.saturating_add(strand_n.saturating_mul(3)));

    with_fib_dirs(ghost_n, |directions| {
        for d in directions {
            let (px, py, z) = pt.project(d[0] * r, d[1] * r, d[2] * r);
            let depth = (z * inv_r + 1.0) / 2.0;
            dots.push(Dot::new(px, py, z, 0.8 * rs, 0.78).with_a(0.1 + 0.22 * depth));
        }
    });

    let turns = o.turns.unwrap_or(3.0);
    let r_base = o.r_base.unwrap_or(1.2);
    let r_depth = o.r_depth.unwrap_or(1.8);
    let inv_strand = 1.0 / strand_n as f32;
    for s in 0..3 {
        let phase = (s as f32 / 3.0) * 2.0 * PI;
        for i in 0..strand_n {
            let u = (frac(i as f32 * inv_strand + t * 0.045) * 2.0 - 1.0) * 0.96;
            let surf = (1.0 - u * u).max(0.0).sqrt();
            let end_fade = ((0.96 - u.abs()) / 0.12).clamp(0.0, 1.0);
            let a = u * PI * turns + phase;
            let weave = 1.0 + 0.075 * (u * PI * turns * 2.0 + phase * 2.0 + t * 0.8).sin();
            let rr = surf * r * weave;
            let (px, py, zr) = pt.project(a.cos() * rr, u * r * weave, a.sin() * rr);
            let depth = (zr * inv_r + 1.0) / 2.0;
            dots.push(
                Dot::new(
                    px,
                    py,
                    zr,
                    (r_base + r_depth * depth) * rs,
                    0.55 - 0.45 * depth,
                )
                .with_a(end_fade * (0.45 + 0.55 * depth)),
            );
        }
    }
}