diff --git a/src/main.rs b/src/main.rs index c4b1546..4dabeb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,167 +1,253 @@ -use std::{ - f32::consts::PI, - pin::pin, - task::{Context, Poll, Waker}, -}; +use std::f32::consts::PI; use flo_draw::{ - canvas::{Color::Rgba, GraphicsContext, GraphicsPrimitives}, + canvas::{Color::Rgba, GraphicsContext, GraphicsPrimitives, Transform2D}, create_drawing_window, create_drawing_window_with_events, with_2d_graphics, }; -use futures::{StreamExt, executor, pin_mut}; -use glam::{FloatExt, Mat2, Vec2, vec2}; +use glam::{Affine2, FloatExt, Vec2, vec2}; const fn rad(deg: f32) -> f32 { deg * (PI / 180.0) } -fn involute(t: f32) -> Vec2 { - let v = Vec2::from_angle(t); - v - t * v.perp() +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 involute_radius_to_t(r: f32) -> f32 { - (r * r - 1.).sqrt() -} - -fn gear_shape_ex(n: usize, inner: f32, outer: f32) -> Vec { - const Q: usize = 6; - - const A: f32 = rad(28.); - let base = A.cos(); - - let base_to_pitch_angle = involute(involute_radius_to_t(1. / base)).to_angle(); - - let tooth_angle = 2.0 * PI / n as f32; - println!("{n}: {base}, {inner}, {outer}"); - let t1 = involute_radius_to_t((inner / base).max(1.)); - let t2 = involute_radius_to_t(outer / base); - println!("{t1}-{t2}"); - println!("{base_to_pitch_angle}, {}", tooth_angle / 4.); - - let mut side = [Vec2::ZERO; Q + 2]; - side[0] = vec2(inner, 0.0); - for k in 0..=Q { - let t01 = k as f32 / Q as f32; - let t = f32::lerp(t1, t2, t01); - side[k + 1] = base * involute(t); - } - - let m = Mat2::from_angle(-(tooth_angle / 4.0 + base_to_pitch_angle)); - for p in &mut side { - *p = m * *p; - } - if base_to_pitch_angle > tooth_angle / 4.0 { - side[0] = Mat2::from_angle(-(tooth_angle / 2.0)) * vec2(inner, 0.0); - } - - let mut ret = Vec::with_capacity(2 * n * (Q + 2)); - for i in 0..n { - let m = Mat2::from_angle(i as f32 * tooth_angle); - for p in side { - ret.push(m * p); - } - for p in side.iter().rev() { - ret.push(m * vec2(p.x, -p.y)); +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(); } - ret + + gc.stroke_color(Rgba(0.1, 1.0, 0.1, 0.2)); + + gc.circle(0.0, -r, r); + gc.stroke(); } -fn gear_shape_std(n: usize, low: f32, high: f32) -> Vec { - let unit = 1.0 / n as f32; - return gear_shape_ex(n, 1.0 - low * unit, 1.0 + high * unit); +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 gear_shape(n: usize) -> Vec { - return gear_shape_std(n, 2.2, 2.0); +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 gear_shape_outer(n: usize) -> Vec { - return gear_shape_std(n, 2.0, 2.2); +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 draw(gc: &mut impl GraphicsContext) { + 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, 0.1, 0.1, 0.1)); + // cut_involute(gc, 24); + + gc.stroke_color(Rgba(0.1, 1.0, 0.1, 0.1)); + cut_crown(gc, 36, 24); } fn main() { - const R_SCALE: f32 = 0.5; - const N_TEETH_SUN: usize = 36; - const N_TEETH_SAT: usize = 24; - const N_TEETH_SKY: usize = N_TEETH_SUN + 2 * N_TEETH_SAT; - const N_SAT: usize = 5; - const R_SUN: f32 = R_SCALE * N_TEETH_SUN as f32; - const R_SAT: f32 = R_SCALE * N_TEETH_SAT as f32; - const OFF_SAT: f32 = R_SUN + R_SAT; - const R_SKY: f32 = R_SCALE * N_TEETH_SKY as f32; - - const FPS: f32 = 60.0; - with_2d_graphics(|| { - let sun = gear_shape(N_TEETH_SUN); - let sat = gear_shape(N_TEETH_SAT); - let crown = gear_shape_outer(N_TEETH_SKY); + let title = "Gear shape test"; + if cfg!(events) { + use futures::StreamExt; + use std::task::{Context, Poll, Waker}; - let (canvas, mut events) = create_drawing_window_with_events("Canvas window"); - let mut t = 0.0f32; - let mut angle_sun = 1.0 / N_TEETH_SUN as f32 * PI; - let mut angle_sky = 0.0; - let mut angle_sat = 0.0; - let mut angle_orbit = 0.0; - 'main: loop { - while let Poll::Ready(event) = - events.poll_next_unpin(&mut Context::from_waker(Waker::noop())) - { - let Some(_event) = event else { break 'main }; + let (canvas, mut events) = create_drawing_window_with_events(title); + '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(draw); } - canvas.draw(|gc| { - gc.clear_canvas(Rgba(0.1, 0.1, 0.1, 1.0)); - - gc.canvas_height(100.0); - gc.line_width(0.25); - - let m = Mat2::from_angle(angle_sun); - gc.new_path(); - for pt in &sun { - let pt = R_SUN * m * pt; - gc.line_to(pt.x, pt.y); - } - gc.close_path(); - gc.stroke_color(Rgba(0.8, 0.7, 0.1, 1.0)); - gc.stroke(); - - let mr = Mat2::from_angle(angle_sat); - for k in 0..N_SAT { - let mo = Mat2::from_angle(angle_orbit + k as f32 / N_SAT as f32 * 2. * PI); - gc.new_path(); - for pt in &sat { - let pt = mo * vec2(OFF_SAT, 0.) + R_SAT * mr * pt; - gc.line_to(pt.x, pt.y); - } - gc.close_path(); - gc.stroke_color(Rgba(0.1, 0.8, 0.1, 1.0)); - gc.stroke(); - } - - let m = Mat2::from_angle(angle_sky); - gc.new_path(); - for pt in &crown { - let pt = R_SKY * m * pt; - gc.line_to(pt.x, pt.y); - } - gc.close_path(); - gc.stroke_color(Rgba(0.1, 0.4, 0.8, 1.0)); - gc.stroke(); - }); - - let v_sun: f32 = 1.0 + 2.0 * (0.007 * t).sin(); - let v_sky: f32 = 1.0 - 2.0 * (0.003 * t).cos(); - let v_sat: f32 = (v_sky - v_sun) / 2.0; - let v_orbit: f32 = (v_sun + v_sky) / 2.0; - - let dt = 1.0 / FPS; - t += dt; - angle_sun += dt * v_sun / N_TEETH_SUN as f32; - angle_sky += dt * v_sky / N_TEETH_SKY as f32; - angle_sat += dt * v_sat / N_TEETH_SAT as f32; - angle_orbit += dt * v_orbit / (N_TEETH_SUN + N_TEETH_SAT) as f32; + } else { + let canvas = create_drawing_window(title); + canvas.draw(draw); } }); }