use super::{Canvas, Pixel, Point, SpotId, SpotShape, Vector};
use crate::pattern::AiryPattern;
impl SpotShape {
#[must_use]
fn effective_radius_xy(&self) -> (f32, f32) {
(
AiryPattern::SIZE_FACTOR * self.xx.hypot(self.xy),
AiryPattern::SIZE_FACTOR * self.yy.hypot(self.yx),
)
}
#[must_use]
pub(super) fn invert(&self) -> SpotShape {
let det = self.xx * self.yy - self.xy * self.yx;
if det.abs() < 0.01 {
debug_assert!(false, "Singular shape matrix: {self:?}");
return SpotShape::default();
}
let inv_det = det.recip();
let xx = inv_det * self.yy;
let yy = inv_det * self.xx;
let xy = inv_det * -self.xy;
let yx = inv_det * -self.yx;
SpotShape { xx, xy, yx, yy }
}
#[must_use]
fn apply(&self, vec: Vector) -> Vector {
let x = vec.0 * self.xx + vec.1 * self.xy;
let y = vec.1 * self.yy + vec.0 * self.yx;
(x, y)
}
}
#[derive(Debug, Clone, Copy)]
struct BoundingBox {
x0: u32,
y0: u32,
x1: u32,
y1: u32,
}
#[allow(
clippy::cast_possible_wrap,
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss
)]
impl BoundingBox {
#[must_use]
fn new(position: Point, shape: &SpotShape, width: u32, height: u32) -> Self {
let (rx, ry) = shape.effective_radius_xy();
let (px, py) = position;
let (w, h) = (width as i32, height as i32);
let x0 = ((px - rx).floor() as i32).max(0).min(w) as u32;
let y0 = ((py - ry).floor() as i32).max(0).min(h) as u32;
let x1 = ((px + rx).ceil() as i32).max(0).min(w) as u32;
let y1 = ((py + ry).ceil() as i32).max(0).min(h) as u32;
BoundingBox { x0, y0, x1, y1 }
}
#[must_use]
fn is_empty(&self) -> bool {
self.x0 == self.x1 || self.y0 == self.y1
}
}
#[allow(
clippy::cast_possible_wrap,
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss
)]
impl Canvas {
pub(super) fn draw_spot(&mut self, spot_id: SpotId) {
let position = self.spot_position(spot_id).unwrap();
let intensity = self.spot_intensity(spot_id).unwrap();
let shape = self.spots[spot_id].shape;
let shape_inv = self.spots[spot_id].shape_inv;
if intensity <= 0.0 {
return;
}
let bbox = BoundingBox::new(position, &shape, self.width, self.height);
if bbox.is_empty() {
return;
}
for i in bbox.y0..bbox.y1 {
let line_off = (i * self.width) as usize;
for j in bbox.x0..bbox.x1 {
let pix_off = line_off + j as usize;
let pixval = self.eval_spot_pixel(position, &shape_inv, intensity, j, i);
self.pixbuf[pix_off] = self.pixbuf[pix_off].saturating_add(pixval);
}
}
}
#[must_use]
fn eval_spot_pixel(
&self,
center: Point,
shape_inv: &SpotShape,
intensity: f32,
x: u32,
y: u32,
) -> Pixel {
let rvec = (((x as f32) - center.0), ((y as f32) - center.1));
let (tx, ty) = shape_inv.apply(rvec);
let rdist = tx.hypot(ty);
let pattern_val = self.pattern.eval(rdist);
(intensity * pattern_val * f32::from(Pixel::MAX)) as Pixel
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calc_radius() {
const RE: f32 = 1.830_9;
const RX: f32 = 6.141_1;
const RY: f32 = 10.235_2;
let shape = SpotShape::default();
let (rx, ry) = shape.effective_radius_xy();
assert!((rx - RE).abs() < 1e-4, "rx = {rx}, RE = {RE}");
assert!((ry - RE).abs() < 1e-4, "ry = {ry}, RE = {RE}");
let shape = SpotShape {
xx: 3.0,
xy: -1.5,
yx: 2.5,
yy: 5.0,
};
let (rx, ry) = shape.effective_radius_xy();
assert!((rx - RX).abs() < 1e-4, "rx = {rx}, RX = {RX}");
assert!((ry - RY).abs() < 1e-4, "ry = {ry}, RY = {RY}");
}
#[test]
fn calc_bbox() {
let shape = SpotShape::default();
let mut position = (7.5, 9.2);
let width = 16;
let height = 16;
let bbox = BoundingBox::new(position, &shape, width, height);
assert!(!bbox.is_empty());
assert_eq!(bbox.x0, 5);
assert_eq!(bbox.x1, 10);
assert_eq!(bbox.y0, 7);
assert_eq!(bbox.y1, 12);
position = (10.5, 13.3);
let bbox = BoundingBox::new(position, &shape, width, height);
assert!(!bbox.is_empty());
assert_eq!(bbox.x0, 8);
assert_eq!(bbox.x1, 13);
assert_eq!(bbox.y0, 11);
assert_eq!(bbox.y1, 16);
position = (-5.5, 20.3);
let bbox = BoundingBox::new(position, &shape, width, height);
assert!(bbox.is_empty());
position = (-1.0, 15.5);
let bbox = BoundingBox::new(position, &shape, width, height);
assert!(!bbox.is_empty());
assert_eq!(bbox.x0, 0);
assert_eq!(bbox.x1, 1);
assert_eq!(bbox.y0, 13);
assert_eq!(bbox.y1, 16);
}
#[test]
fn calc_bbox_rect() {
let shape = SpotShape {
xx: 3.0,
xy: -1.5,
yx: 2.5,
yy: 5.0,
};
let mut position = (7.5, 9.2);
let width = 32;
let height = 32;
let bbox = BoundingBox::new(position, &shape, width, height);
assert!(!bbox.is_empty());
assert_eq!(bbox.x0, 1);
assert_eq!(bbox.x1, 14);
assert_eq!(bbox.y0, 0);
assert_eq!(bbox.y1, 20);
position = (10.5, 13.3);
let bbox = BoundingBox::new(position, &shape, width, height);
assert!(!bbox.is_empty());
assert_eq!(bbox.x0, 4);
assert_eq!(bbox.x1, 17);
assert_eq!(bbox.y0, 3);
assert_eq!(bbox.y1, 24);
position = (-15.5, 20.3);
let bbox = BoundingBox::new(position, &shape, width, height);
assert!(bbox.is_empty());
position = (-5.0, 15.5);
let bbox = BoundingBox::new(position, &shape, width, height);
assert!(!bbox.is_empty());
assert_eq!(bbox.x0, 0);
assert_eq!(bbox.x1, 2);
assert_eq!(bbox.y0, 5);
assert_eq!(bbox.y1, 26);
}
#[test]
fn draw_spot() {
let shape = SpotShape::default();
let mut c = Canvas::new(8, 8);
let spot1 = c.add_spot((1.1, 4.3), shape, 0.3);
let spot2 = c.add_spot((4.6, 7.2), shape, 0.4);
let spot3 = c.add_spot((6.8, 2.6), shape, 0.4);
let spot4 = c.add_spot((5.1, 4.6), shape, 0.2);
c.draw_spot(spot1);
assert_eq!(c.pixbuf[8 * 4 + 1], 13449);
c.draw_spot(spot2);
assert_eq!(c.pixbuf[8 * 7 + 5], 11960);
c.draw_spot(spot3);
assert_eq!(c.pixbuf[8 * 3 + 7], 11960);
c.draw_spot(spot4);
assert_eq!(c.pixbuf[8 * 5 + 5], 6755);
}
}