use crate::draw_utils::{filled_disk, filled_rect, segment, thick_segment};
use embassy_ssd1306_graphics::Graphics;
use embedded_hal_async::i2c::I2c;
#[derive(Clone, Copy, Debug)]
pub struct Transform2D {
sx: f32,
sy: f32,
shx: f32,
shy: f32,
}
impl Transform2D {
#[inline]
pub const fn identity() -> Self {
Self { sx: 1.0, sy: 1.0, shx: 0.0, shy: 0.0 }
}
#[inline]
pub const fn mirror_x() -> Self {
Self { sx: -1.0, sy: 1.0, shx: 0.0, shy: 0.0 }
}
#[inline]
fn apply(self, vx: f32, vy: f32) -> (f32, f32) {
(
self.sx * vx + self.shx * vy,
self.shy * vx + self.sy * vy,
)
}
#[inline]
fn endpoint(self, ox: i32, oy: i32, lx: f32, ly: f32, len: i32) -> (i32, i32) {
let (dx, dy) = self.apply(lx, ly);
(
ox + (dx * len as f32 + 0.5) as i32,
oy + (dy * len as f32 + 0.5) as i32,
)
}
#[inline]
fn perp(gx: f32, gy: f32) -> (f32, f32) {
(-gy, gx)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Facing {
Right,
Left,
}
impl Facing {
#[inline]
pub fn transform(self) -> Transform2D {
match self {
Facing::Right => Transform2D::identity(),
Facing::Left => Transform2D::mirror_x(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct RoboticArm {
pub base_x: i32,
pub base_y: i32,
pub seg1_len: i32,
pub seg2_len: i32,
pub wall_w: i32,
pub wall_h: i32,
pub gripper_len: i32,
pub gripper_thickness: i32,
}
impl RoboticArm {
pub fn new(base_x: i32, base_y: i32, seg1_len: i32, seg2_len: i32) -> Self {
Self {
base_x,
base_y,
seg1_len,
seg2_len,
wall_w: 20,
wall_h: 6,
gripper_len: 10,
gripper_thickness: 2,
}
}
pub fn with_wall(mut self, wall_w: i32, wall_h: i32) -> Self {
self.wall_w = wall_w;
self.wall_h = wall_h;
self
}
pub fn with_gripper(mut self, gripper_len: i32, gripper_thickness: i32) -> Self {
self.gripper_len = gripper_len;
self.gripper_thickness = gripper_thickness;
self
}
pub fn draw<I: I2c>(
&self,
gfx: &mut Graphics<'_, I>,
angle_shoulder: f32,
angle_elbow: f32,
gripper_opening: f32,
facing: Facing,
on: bool,
cos_fn: fn(f32) -> f32,
sin_fn: fn(f32) -> f32,
) {
let t = facing.transform();
let half = self.wall_w / 2;
let sx = self.base_x - half;
let sy = self.base_y - self.wall_h;
filled_rect(gfx, sx, sy, self.wall_w, self.wall_h, on);
let mut d = 0;
while d < self.wall_w + self.wall_h {
for dx in 0..self.wall_w {
let dy = d - dx;
if dy >= 0 && dy < self.wall_h {
gfx.pixel(sx + dx, sy + dy, !on);
}
}
d += 4;
}
segment(gfx, sx - 4, self.base_y, sx + self.wall_w + 4, self.base_y, on);
let shoulder_x = self.base_x;
let shoulder_y = sy;
let (l1x, l1y) = (sin_fn(angle_shoulder), -cos_fn(angle_shoulder));
let (g1x, g1y) = t.apply(l1x, l1y);
let (elbow_x, elbow_y) = t.endpoint(shoulder_x, shoulder_y, l1x, l1y, self.seg1_len);
let abs_angle = angle_shoulder + angle_elbow;
let (l2x, l2y) = (sin_fn(abs_angle), -cos_fn(abs_angle));
let (g2x, g2y) = t.apply(l2x, l2y);
let (end_x, end_y) = t.endpoint(elbow_x, elbow_y, l2x, l2y, self.seg2_len);
let (perp_x, perp_y) = Transform2D::perp(g2x, g2y);
let _ = (g1x, g1y);
thick_segment(gfx, shoulder_x, shoulder_y, elbow_x, elbow_y, on);
thick_segment(gfx, elbow_x, elbow_y, end_x, end_y, on);
filled_disk(gfx, shoulder_x, shoulder_y, 3, on);
filled_disk(gfx, elbow_x, elbow_y, 3, on);
filled_disk(gfx, end_x, end_y, 2, on);
let body_len = 3_i32;
let guide_x = end_x + (g2x * body_len as f32 + 0.5) as i32;
let guide_y = end_y + (g2y * body_len as f32 + 0.5) as i32;
segment(gfx, end_x, end_y, guide_x, guide_y, on);
let open = gripper_opening.clamp(0.0, 1.0);
let half_gap_max = (self.gripper_len as f32 * 0.6) as i32;
let half_gap_closed = self.gripper_thickness;
let half_gap = half_gap_closed
+ ((half_gap_max - half_gap_closed) as f32 * open) as i32;
let jaw1_ox = guide_x + (perp_x * half_gap as f32 + 0.5) as i32;
let jaw1_oy = guide_y + (perp_y * half_gap as f32 + 0.5) as i32;
let jaw2_ox = guide_x - (perp_x * half_gap as f32 + 0.5) as i32;
let jaw2_oy = guide_y - (perp_y * half_gap as f32 + 0.5) as i32;
let jaw1_ex = jaw1_ox + (g2x * self.gripper_len as f32 + 0.5) as i32;
let jaw1_ey = jaw1_oy + (g2y * self.gripper_len as f32 + 0.5) as i32;
let jaw2_ex = jaw2_ox + (g2x * self.gripper_len as f32 + 0.5) as i32;
let jaw2_ey = jaw2_oy + (g2y * self.gripper_len as f32 + 0.5) as i32;
segment(gfx, jaw1_ox, jaw1_oy, jaw2_ox, jaw2_oy, on);
segment(gfx, jaw1_ox, jaw1_oy, jaw1_ex, jaw1_ey, on);
segment(gfx, jaw2_ox, jaw2_oy, jaw2_ex, jaw2_ey, on);
let tip = self.gripper_thickness.max(2);
let tip_off_x = (perp_x * tip as f32 + 0.5) as i32;
let tip_off_y = (perp_y * tip as f32 + 0.5) as i32;
segment(gfx,
jaw1_ex + tip_off_x, jaw1_ey + tip_off_y,
jaw1_ex - tip_off_x, jaw1_ey - tip_off_y,
on,
);
segment(gfx,
jaw2_ex + tip_off_x, jaw2_ey + tip_off_y,
jaw2_ex - tip_off_x, jaw2_ey - tip_off_y,
on,
);
}
pub fn erase<I: I2c>(
&self,
gfx: &mut Graphics<'_, I>,
angle_shoulder: f32,
angle_elbow: f32,
gripper_opening: f32,
facing: Facing,
cos_fn: fn(f32) -> f32,
sin_fn: fn(f32) -> f32,
) {
self.draw(
gfx,
angle_shoulder,
angle_elbow,
gripper_opening,
facing,
false, cos_fn,
sin_fn,
);
}
}