gear-teeth/src/main.rs
2026-10-08 18:43:22 +03:00

363 lines
9.6 KiB
Rust

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<Item = Vec2>) {
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);
}
}