use plotive::des;
mod common;
const START_HEIGHT: f64 = 60.0;
const BOUNCE_RESTITUTION: f64 = 0.76;
type Time = f64;
type State = ode_solvers::Vector2<f64>; const H: usize = 0;
const V: usize = 1;
struct BouncingBall;
impl ode_solvers::System<Time, State> for BouncingBall {
fn system(&self, _t: Time, y: &State, dy: &mut State) {
let cd = 0.5;
let m = 0.058;
let d = 0.065;
let s = core::f64::consts::PI * d * d / 4.0;
let rho = 1.225;
let g = 9.81;
let vel = y[1];
let drag_force = 0.5 * rho * vel * vel * s * cd;
let drag_direction = if vel >= 0.0 { 1.0 } else { -1.0 };
dy[H] = y[V];
dy[V] = -g - drag_direction * drag_force / m;
}
fn solout(&mut self, _x: Time, y: &State, _dy: &State) -> bool {
y[H] < 0.
}
}
struct BouncingBallData {
time: Vec<f64>,
location: Vec<f64>,
velocity: Vec<f64>,
}
impl BouncingBallData {
fn calculate() -> Self {
let mut res = BouncingBallData {
time: Vec::new(),
location: Vec::new(),
velocity: Vec::new(),
};
let mut x0 = 0.0;
let mut y0 = State::new(START_HEIGHT, 0.0);
let x_end = 10.0;
let mut bounce = 1;
let max_bounce = 10;
loop {
let bb = BouncingBall;
let mut stepper = ode_solvers::Dopri5::new(bb, x0, x_end, 0.01, y0, 1E-6, 1E-6);
let stats = stepper.integrate().unwrap();
println!("Bounce {bounce}\n{stats}\n");
bounce += 1;
let (x, y) = stepper.results().get();
let rebounce = y
.iter()
.enumerate()
.find(|(_i, y)| y[H] <= 0.0)
.map(|(i, _y)| i);
let (x, y) = if let Some(touch_ground) = rebounce {
(&x[..=touch_ground], &y[..=touch_ground])
} else {
(x.as_slice(), y.as_slice())
};
res.time.extend(x.iter());
res.location.extend(y.iter().map(|y| y[0]));
res.velocity.extend(y.iter().map(|y| y[1]));
let Some(rebounce) = rebounce else { break };
let x = &x[..=rebounce];
let y = &y[..=rebounce];
x0 = *x.last().unwrap();
y0 = *y.last().unwrap();
y0[H] = y0[H].abs();
y0[V] *= -BOUNCE_RESTITUTION;
if x0 >= x_end || bounce >= max_bounce {
break;
}
}
res
}
}
fn main() {
let data = BouncingBallData::calculate();
let mut data_source = plotive::data::NamedColumns::new();
data_source.add_column("time", &data.time);
data_source.add_column("height", &data.location);
data_source.add_column("velocity", &data.velocity);
let title = "Tennis ball thrown from 1\u{02E2}\u{1D57} floor of Eiffel Tower";
let fig = des::Figure::new(
des::Plot::new(vec![
des::series::Line::new(des::data_src_ref("time"), des::data_src_ref("height"))
.with_name("Height (m)")
.into(),
des::series::Line::new(des::data_src_ref("time"), des::data_src_ref("velocity"))
.with_name("Velocity (m/s)")
.into(),
])
.with_x_axis(
des::Axis::new()
.with_ticks(
des::axis::Ticks::new()
.with_locator(des::axis::ticks::TimeDeltaLocator::Seconds(2).into())
.with_formatter(Some(
des::axis::ticks::TimeDeltaFormatter::Custom("%M:%S".to_string())
.into(),
)),
)
.with_grid(Default::default())
.with_title("Time".into()),
)
.with_y_axis(
des::Axis::new()
.with_ticks(des::axis::Ticks::new())
.with_grid(Default::default()),
)
.with_annotation(des::annot::Line::horizontal(0.0).into())
.with_legend(des::plot::LegendPos::InTopRight.into())
.into(),
)
.with_title(title.into());
common::save_figure(&fig, &data_source, None, "bouncing_ball");
}