use super::EDGE_PX;
use super::shape::{Rib, Shape};
enum Flat {
Ribbon {
ribs: Vec<Rib>,
fill: egui::Color32,
},
Disc {
cx: f32,
cy: f32,
radius: f32,
fill: egui::Color32,
},
}
pub(super) fn rgba(shapes: &[Shape], size: u16) -> Vec<u8> {
let side = f32::from(size);
let edge = EDGE_PX / side;
let flats: Vec<Flat> = shapes.iter().map(Flat::of).collect();
let mut out = Vec::with_capacity(usize::from(size) * usize::from(size) * 4);
for y in 0..size {
for x in 0..size {
let point = sample(
&flats,
(f32::from(x) + 0.5) / side,
(f32::from(y) + 0.5) / side,
edge,
);
out.extend_from_slice(&point);
}
}
out
}
#[derive(Default)]
struct Paint {
red: f32,
green: f32,
blue: f32,
alpha: f32,
}
impl Paint {
fn over(&mut self, fill: egui::Color32, cover: f32) {
let keep = 1.0 - cover;
let mix =
|channel: u8, under: f32| (f32::from(channel) / 255.0).mul_add(cover, under * keep);
self.red = mix(fill.r(), self.red);
self.green = mix(fill.g(), self.green);
self.blue = mix(fill.b(), self.blue);
self.alpha = cover + self.alpha * keep;
}
fn straight(self) -> [u8; 4] {
if self.alpha <= 0.0 {
return [0, 0, 0, 0];
}
let byte = |value: f32| (value.clamp(0.0, 1.0) * 255.0) as u8;
[
byte(self.red / self.alpha),
byte(self.green / self.alpha),
byte(self.blue / self.alpha),
byte(self.alpha),
]
}
}
fn sample(shapes: &[Flat], px: f32, py: f32, edge: f32) -> [u8; 4] {
let mut paint = Paint::default();
for shape in shapes {
let cover = shape.cover(px, py, edge);
if cover > 0.0 {
paint.over(shape.fill(), cover);
}
}
paint.straight()
}
impl Flat {
fn of(shape: &Shape) -> Self {
match shape {
Shape::Disc {
cx,
cy,
radius,
fill,
} => Self::Disc {
cx: *cx,
cy: *cy,
radius: *radius,
fill: *fill,
},
ribbon => Self::Ribbon {
ribs: ribbon.ribs(),
fill: ribbon.fill(),
},
}
}
fn fill(&self) -> egui::Color32 {
match self {
Self::Ribbon { fill, .. } | Self::Disc { fill, .. } => *fill,
}
}
fn cover(&self, px: f32, py: f32, feather: f32) -> f32 {
match self {
Self::Disc { cx, cy, radius, .. } => {
((radius - (px - cx).hypot(py - cy)) / feather).clamp(0.0, 1.0)
}
Self::Ribbon { ribs, .. } => {
let mut best: f32 = 0.0;
for (near, far) in ribs.iter().zip(ribs.iter().skip(1)) {
best = best.max(quad(
(px, py),
[near.out, far.out, far.back, near.back],
feather,
));
}
best
}
}
}
}
fn quad(point: (f32, f32), corners: [(f32, f32); 4], feather: f32) -> f32 {
let mid = (
corners.iter().map(|corner| corner.0).sum::<f32>() / 4.0,
corners.iter().map(|corner| corner.1).sum::<f32>() / 4.0,
);
let mut inside = f32::MAX;
for (edge, (from, to)) in corners
.iter()
.zip(corners.iter().cycle().skip(1))
.enumerate()
{
let (ex, ey) = (to.0 - from.0, to.1 - from.1);
let span = ex.hypot(ey);
let side = |at: (f32, f32)| ((at.0 - from.0) * ey - (at.1 - from.1) * ex) / span;
let signed = side(point) * side(mid).signum();
inside = inside.min(if edge % 2 == 0 {
signed / feather
} else if signed >= 0.0 {
f32::INFINITY
} else {
f32::NEG_INFINITY
});
}
(inside + 0.5).clamp(0.0, 1.0)
}