use std::time::Duration; use bevy::prelude::*; use bevy_inspector_egui::{Inspectable, RegisterInspectable}; use bevy_prototype_lyon::prelude::*; use bevy_tweening::{ component_animator_system, lens::TransformScaleLens, Animator, EaseFunction, Tween, TweenCompleted, TweeningPlugin, TweeningType, }; #[allow(unused_imports)] use crate::paths::{circle_star::circle_star, geometry_task::geometry_task}; use crate::{ datatypes::{angle::Angle, length::Length}, primitives::{ animation::{CircleAnimationLens, LineAnimationLens}, bundles::{Turtle, TurtleDrawCircle, TurtleDrawLine}, }, turtle_shapes, }; pub struct TurtlePlugin; impl Plugin for TurtlePlugin { fn build(&self, app: &mut bevy::prelude::App) { app.add_plugin(TweeningPlugin) .add_startup_system(setup) .add_system(keypresses) .add_system(draw_lines) .add_system(component_animator_system::) .register_inspectable::() .register_inspectable::(); } } pub type Precision = f32; #[derive(Component, Inspectable)] pub struct TurtleCommands { commands: Vec, lines: Vec, state: TurtleState, } #[derive(Inspectable)] pub struct TurtleState { pub start: Vec2, pub heading: Angle, pub speed: u64, pub index: u64, pub drawing: bool, } impl TurtleCommands { pub fn new(commands: Vec) -> Self { Self { commands, lines: vec![], state: TurtleState { start: Vec2::ZERO, heading: Angle::degrees(0.), speed: 2000, index: 0, drawing: true, }, } } } impl TurtleCommands { fn get_next(&mut self) -> Option<(Option>, Option)> { let index = self.state.index; let next_index = index + 1; if let Some(command) = self.commands.get(self.state.index as usize) { let res = match command { TurtleCommand::Forward(Length(x)) => { crate::turtle_movement::turtle_move(&mut self.state, *x as f32) } TurtleCommand::Backward(Length(x)) => { crate::turtle_movement::turtle_move(&mut self.state, -*x as f32) } TurtleCommand::Left(angle) => { crate::turtle_movement::turtle_turn(&mut self.state, *angle) } TurtleCommand::Right(angle) => { crate::turtle_movement::turtle_turn(&mut self.state, -angle) } TurtleCommand::PenUp => { self.state.drawing = false; (None, None) } TurtleCommand::PenDown => { self.state.drawing = true; (None, None) } TurtleCommand::Circle { radius, angle } => { crate::turtle_movement::turtle_circle(&mut self.state, radius.0 as f32, *angle) } TurtleCommand::Pause => todo!(), }; self.state.index = next_index; Some(res) } else { None } } } #[derive(Inspectable, Default)] pub enum TurtleGraphElement { TurtleLine { start: Vec2, end: Vec2, }, TurtleCircle { start: Vec2, end: Vec2, center: Vec2, radii: Vec2, angle: Angle, }, #[default] Noop, } #[derive(Clone, Component, Inspectable)] pub struct TurtleShape; #[derive(Clone, Component, Inspectable, Default)] pub struct Colors { color: Color, fill_color: Color, } #[derive(Component, Inspectable, Default)] pub enum TurtleCommand { Forward(Length), Backward(Length), Left(Angle), Right(Angle), PenUp, PenDown, #[default] Pause, Circle { radius: Length, angle: Angle, }, } fn setup(mut commands: Commands) { let animator = Animator::new(Tween::new( EaseFunction::QuadraticInOut, TweeningType::PingPong, Duration::from_millis(500), TransformScaleLens { start: Vec3::new(1., 1., 0.), end: Vec3::new(1.3, 1.3, 0.), }, )); commands.spawn_bundle(Camera2dBundle::default()); let mut turtle_bundle = Turtle::default(); turtle_bundle.set_commands(geometry_task()); commands .spawn_bundle(turtle_bundle) .insert_bundle(GeometryBuilder::build_as( &turtle_shapes::turtle(), DrawMode::Outlined { fill_mode: FillMode::color(Color::MIDNIGHT_BLUE), outline_mode: StrokeMode::new(Color::BLACK, 1.0), }, Transform::identity(), )) .insert(animator) .insert(TurtleShape); } fn draw_lines( mut commands: Commands, mut tcmd: Query<&mut TurtleCommands>, mut turtle: Query<&mut Animator, With>, mut query_event: EventReader, // TODO: howto attach only to the right event? ) { for ev in query_event.iter() { let mut tcmd = tcmd.single_mut(); loop { match tcmd.get_next() { Some((Some(turtle_animation), Some(graph_element_to_draw))) => { let mut turtle = turtle.single_mut(); turtle.set_tweenable(turtle_animation); match graph_element_to_draw { TurtleGraphElement::TurtleLine { start, end } => { let line_animator = Animator::new(Tween::new( EaseFunction::QuadraticInOut, TweeningType::Once, Duration::from_millis(tcmd.state.speed), LineAnimationLens::new(start, end), )); commands .spawn_bundle(TurtleDrawLine::new(start, end, tcmd.state.index)) .insert(line_animator); } TurtleGraphElement::Noop => println!("No drawing!"), TurtleGraphElement::TurtleCircle { center, radii, angle, start, end, } => { let line_animator = Animator::new(Tween::new( EaseFunction::QuadraticInOut, TweeningType::Once, Duration::from_millis(tcmd.state.speed), CircleAnimationLens { start_pos: start, center, radii, start: Angle::degrees(0.), end: angle, }, )); commands .spawn_bundle(TurtleDrawCircle::new( center, radii, Angle::degrees(0.), tcmd.state.index, start, end, )) .insert(line_animator); } } return; } Some((_, _)) => { println!("without animation"); } None => { println!("nothing to draw"); return; } }; } } } fn keypresses( mut commands: Commands, keys: Res>, mut qry: Query<&mut Animator, With>, mut tcmd: Query<&mut TurtleCommands>, ) { if keys.just_pressed(KeyCode::W) { let mut tcmd = tcmd.single_mut(); tcmd.state = TurtleState { start: Vec2::ZERO, heading: Angle::degrees(0.), speed: 2000, index: 0, drawing: true, }; if let Some((Some(turtle_animation), Some(graph_element_to_draw))) = tcmd.get_next() { let mut shap = qry.single_mut(); shap.set_tweenable(turtle_animation); match graph_element_to_draw { TurtleGraphElement::TurtleLine { start, end } => { let line_animator = Animator::new(Tween::new( EaseFunction::QuadraticInOut, TweeningType::Once, Duration::from_millis(tcmd.state.speed), LineAnimationLens::new(start, end), )); commands .spawn_bundle(TurtleDrawLine::new(start, end, tcmd.state.index)) .insert(line_animator); } TurtleGraphElement::Noop => (), TurtleGraphElement::TurtleCircle { center, radii, angle, start, end, } => { commands.spawn_bundle(TurtleDrawCircle::new( center, radii, angle, tcmd.state.index, start, end, )); } } }; } }