use std::{f32::consts::PI, time::Duration}; use flo_draw::{ canvas::{Color::Rgba, GraphicsContext, GraphicsPrimitives, Transform2D}, create_drawing_window, create_drawing_window_with_events, with_2d_graphics, }; use glam::{Affine2, FloatExt, Vec2, vec2}; const fn rad(deg: f32) -> f32 { deg * (PI / 180.0) } const ADDENDUM: f32 = 1.00; const DEDENDUM: f32 = 1.25; const PRESSURE_ANGLE: f32 = rad(20.0); const PITCH: f32 = PI; fn draw_rack(gc: &mut impl GraphicsContext) { gc.move_to(-ADDENDUM * PRESSURE_ANGLE.tan(), ADDENDUM); gc.line_to(DEDENDUM * PRESSURE_ANGLE.tan(), -DEDENDUM); gc.line_to(0.5 * PITCH - DEDENDUM * PRESSURE_ANGLE.tan(), -DEDENDUM); gc.line_to(0.5 * PITCH + ADDENDUM * PRESSURE_ANGLE.tan(), ADDENDUM); gc.line_to(PITCH - ADDENDUM * PRESSURE_ANGLE.tan(), ADDENDUM); gc.line_to(PITCH + DEDENDUM * PRESSURE_ANGLE.tan(), -DEDENDUM); gc.stroke(); gc.stroke_color(Rgba(0.1, 1.0, 0.1, 0.2)); gc.move_to(-PITCH, 0.0); gc.line_to(PITCH, 0.0); gc.stroke(); } fn cut_involute(gc: &mut impl GraphicsContext, n_teeth: u32) { let tooth_angle = 360.0 / n_teeth as f32; let r = 0.5 * n_teeth as f32; let n = 500; for k in -n..=n { let t = 2.0 * k as f32 / n as f32; gc.push_state(); gc.transform(Transform2D::translate(0.0, -r)); gc.transform(Transform2D::rotate_degrees(tooth_angle * t)); gc.transform(Transform2D::translate(0.0, r)); gc.transform(Transform2D::translate(PITCH * t, 0.0)); gc.transform(Transform2D::translate(-2.0 * PITCH, 0.0)); for _ in -2..2 { gc.move_to(-ADDENDUM * PRESSURE_ANGLE.tan(), ADDENDUM); gc.line_to(DEDENDUM * PRESSURE_ANGLE.tan(), -DEDENDUM); gc.line_to(0.5 * PITCH - DEDENDUM * PRESSURE_ANGLE.tan(), -DEDENDUM); gc.line_to(0.5 * PITCH + ADDENDUM * PRESSURE_ANGLE.tan(), ADDENDUM); gc.line_to(PITCH - ADDENDUM * PRESSURE_ANGLE.tan(), ADDENDUM); gc.line_to(PITCH + DEDENDUM * PRESSURE_ANGLE.tan(), -DEDENDUM); gc.stroke(); gc.transform(Transform2D::translate(PITCH, 0.0)); } gc.pop_state(); } gc.stroke_color(Rgba(0.1, 1.0, 0.1, 0.2)); gc.circle(0.0, -r, r); gc.stroke(); } fn show_involute(gc: &mut impl GraphicsContext, n_teeth: u32) { let tooth_angle = 360.0 / n_teeth as f32; let r = 0.5 * n_teeth as f32; let n = 500; let size = 5.0; for k in -n..=n { let t = 2.0 * k as f32 / n as f32; gc.push_state(); gc.transform(Transform2D::translate(0.0, -r)); gc.transform(Transform2D::rotate_degrees(tooth_angle * t)); gc.transform(Transform2D::translate(0.0, r)); gc.transform(Transform2D::translate(PITCH * t, 0.0)); gc.move_to(-size * PRESSURE_ANGLE.tan(), size); gc.line_to(size * PRESSURE_ANGLE.tan(), -size); gc.stroke(); gc.pop_state(); } gc.stroke_color(Rgba(0.1, 1.0, 0.1, 0.2)); gc.circle(0.0, -r, r); gc.circle(0.0, -r, r - DEDENDUM); gc.circle(0.0, -r, r + ADDENDUM); gc.stroke(); } fn draw_crown(gc: &mut impl GraphicsContext, n_teeth: u32) { let tooth_angle = rad(360.0 / n_teeth as f32); let r = 0.5 * n_teeth as f32; let mut points = Vec::new(); let n = 500; for i_point in -n..=n { let t_point = 5.0 * i_point as f32 / n as f32; let x = PITCH * t_point; let angle = f32::atan2(x, r); let tooth = angle / tooth_angle; let vx = PITCH * tooth.fract_gl(); let tan = PRESSURE_ANGLE.tan(); let vy = if vx < DEDENDUM * tan { -vx / tan } else if vx <= 0.5 * PITCH - DEDENDUM * tan { -DEDENDUM } else if vx <= 0.5 * PITCH + ADDENDUM * tan { (vx - 0.5 * PITCH) / tan } else if vx <= PITCH - ADDENDUM * tan { ADDENDUM } else { -(vx - PITCH) / tan }; points.push(vec2(x, vy)); } gc.move_to(points[0].x, points[0].y); for pt in &points[1..] { gc.line_to(pt.x, pt.y); } gc.stroke(); gc.stroke_color(Rgba(0.1, 1.0, 0.1, 0.2)); gc.move_to(-20.0, 0.0); gc.line_to(20.0, 0.0); gc.stroke(); let n = 4; for k in -n..=n { let angle = tooth_angle * k as f32; let x = r * angle.tan(); gc.move_to(x, -1.0); gc.line_to(x, 1.0); gc.stroke(); } } fn cut_crown(gc: &mut impl GraphicsContext, n_teeth_crown: u32, n_teeth_gear: u32) { let tooth_angle_crown = rad(360.0 / n_teeth_crown as f32); let r_crown = 0.5 * n_teeth_crown as f32; let tooth_angle_gear = rad(360.0 / n_teeth_gear as f32); let r_gear = 0.5 * n_teeth_gear as f32; let n_subcuts = 200; for i_subcut in -n_subcuts..=n_subcuts { let t_cut = 2.0 * i_subcut as f32 / n_subcuts as f32; let rot_gear = t_cut * tooth_angle_gear; let rot_crown = t_cut * tooth_angle_crown; let tfm_gear = Affine2::from_translation(vec2(0.0, -r_gear)) * Affine2::from_angle_translation(-rot_gear, Vec2::ZERO) * Affine2::from_translation(vec2(0.0, r_gear)); let mut points = Vec::new(); let n_points = 300; for i_point in -n_points..=n_points { let t_point = 2.0 * i_point as f32 / n_points as f32; let x = PITCH * t_point; let angle = f32::atan2(x, r_crown) + rot_crown; let tooth = angle / tooth_angle_crown; let vx = PITCH * tooth.fract_gl(); let tan = PRESSURE_ANGLE.tan(); let vy = if vx < DEDENDUM * tan { -vx / tan } else if vx <= 0.5 * PITCH - DEDENDUM * tan { -DEDENDUM } else if vx <= 0.5 * PITCH + ADDENDUM * tan { (vx - 0.5 * PITCH) / tan } else if vx <= PITCH - ADDENDUM * tan { ADDENDUM } else { -(vx - PITCH) / tan }; points.push(tfm_gear.transform_point2(vec2(x, vy))); } gc.move_to(points[0].x, points[0].y); for pt in &points[1..] { gc.line_to(pt.x, pt.y); } gc.stroke(); } gc.stroke_color(Rgba(0.1, 1.0, 0.1, 0.2)); gc.move_to(-20.0, 0.0); gc.line_to(20.0, 0.0); gc.stroke(); let n = 4; for k in -n..=n { let angle = tooth_angle_crown * k as f32; let x = r_crown * angle.tan(); gc.move_to(x, -1.0); gc.line_to(x, 1.0); gc.stroke(); } gc.circle(0.0, -r_gear, r_gear); gc.circle(0.0, -r_gear, r_gear - DEDENDUM); gc.circle(0.0, -r_gear, r_gear + ADDENDUM); gc.stroke(); let n = 4; for k in -n..=n { let angle = tooth_angle_gear * k as f32; let (x, y) = angle.sin_cos(); gc.move_to(0.0, -r_gear); gc.line_to(x * r_gear, (y - 1.0) * r_gear); gc.stroke(); } } fn cutter_y_at_x(x: f32) -> f32 { let x = (x / PITCH).fract_gl() * PITCH; let a = DEDENDUM * vec2(PRESSURE_ANGLE.tan(), 1.0); let d = ADDENDUM * vec2(PRESSURE_ANGLE.tan(), -1.0); let slope = 1.0 / PRESSURE_ANGLE.tan(); if x < d.x { return -x * slope; } if x <= 0.5 * PITCH - d.x { return d.y; } if x <= 0.5 * PITCH + a.x { return (x - 0.5 * PITCH) * slope; } if x <= PITCH - a.x { return a.y; } return (PITCH - x) * slope; } const FPS: f32 = 60.0; const TPS: f32 = 1.0; fn main() { with_2d_graphics(|| { let title = "Gear shape test"; use futures::StreamExt; use std::task::{Context, Poll, Waker}; let (canvas, mut events) = create_drawing_window_with_events(title); let mut t = 0.0; let n_rack_teeth = 10; let n_teeth = 36; let n_teeth_sample = n_teeth; let r_pitch = 0.5 * n_teeth as f32; let n_points_per_tooth = 100; let n_points = n_points_per_tooth * n_teeth_sample; let mut shape = vec![r_pitch + ADDENDUM; n_points]; 'main: loop { while let Poll::Ready(event) = events.poll_next_unpin(&mut Context::from_waker(Waker::noop())) { let Some(_event) = event else { break 'main }; } canvas.draw(|gc| { gc.clear_canvas(Rgba(0.1, 0.1, 0.1, 1.0)); gc.canvas_height(20.0); gc.line_width(0.05); gc.stroke_color(Rgba(1.0, 1.0, 1.0, 0.1)); gc.move_to(-20.0, 0.0); gc.line_to(20.0, 0.0); gc.move_to(0.0, -20.0); gc.line_to(0.0, 20.0); gc.stroke(); let gear_offset = t * TPS - (n_teeth_sample as f32 + 5.0); let rack_offset = (t * TPS).fract_gl(); let kr_to_xy = |k, r: f32| { let u = k as f32 / n_points_per_tooth as f32; let angle = 2.0 * PI * (gear_offset + u) / n_teeth as f32; let (s, c) = angle.sin_cos(); vec2(s * r, r_pitch - c * r) }; for (k, r) in shape.iter_mut().enumerate() { loop { let xy = kr_to_xy(k, *r); let cut_y = cutter_y_at_x(xy.x - rack_offset * PITCH); if cut_y < xy.y { break; } *r -= 0.01; } } #[cfg(false)] { line( gc, shape.iter().enumerate().map(|(k, r)| { let xy = kr_to_xy(k, *r); let cut_y = cutter_y_at_x(xy.x - rack_offset * PITCH); vec2(xy.x, cut_y) }), ); gc.stroke_color(Rgba(1.0, 1.0, 0.1, 0.4)); gc.stroke(); } line(gc, shape.iter().enumerate().map(|(k, r)| kr_to_xy(k, *r))); gc.close_path(); gc.stroke_color(Rgba(0.1, 0.6, 1.0, 0.4)); gc.stroke(); let mut pts = Vec::new(); for tooth in -n_rack_teeth..=n_rack_teeth { let x = (tooth as f32 + rack_offset) * PITCH; let a = DEDENDUM * vec2(PRESSURE_ANGLE.tan(), 1.0); let d = ADDENDUM * vec2(PRESSURE_ANGLE.tan(), -1.0); pts.push(vec2(x - 0.5 * PITCH - d.x, d.y)); pts.push(vec2(x - 0.5 * PITCH + a.x, a.y)); pts.push(vec2(x - a.x, a.y)); pts.push(vec2(x + d.x, d.y)); } line(gc, pts); gc.stroke_color(Rgba(1.0, 0.1, 0.1, 0.4)); gc.stroke(); let mut pts = Vec::new(); for tooth in -n_rack_teeth..=n_rack_teeth { let x = (tooth as f32 + rack_offset) * PITCH; let a = ADDENDUM * vec2(PRESSURE_ANGLE.tan(), 1.0); let d = DEDENDUM * vec2(PRESSURE_ANGLE.tan(), -1.0); pts.push(vec2(x - 0.5 * PITCH - d.x, d.y)); pts.push(vec2(x - 0.5 * PITCH + a.x, a.y)); pts.push(vec2(x - a.x, a.y)); pts.push(vec2(x + d.x, d.y)); } line(gc, pts); gc.stroke_color(Rgba(0.1, 1.0, 0.1, 0.4)); gc.stroke(); }); std::thread::sleep(Duration::from_secs_f32(1.0 / FPS)); t += 1.0 / FPS; } }); } fn line(gc: &mut impl GraphicsContext, pts: impl IntoIterator) { let mut pts = pts.into_iter(); let Some(pt) = pts.next() else { return; }; gc.move_to(pt.x, pt.y); for pt in pts { gc.line_to(pt.x, pt.y); } }