diff --git a/src/animation_step.rs b/src/animation_step.rs new file mode 100644 index 0000000..438cd75 --- /dev/null +++ b/src/animation_step.rs @@ -0,0 +1,53 @@ +use bevy::prelude::{Commands, Query, Transform, With}; +use bevy_tweening::Animator; + +use crate::{ + turtle::{TurtleCommands, TurtleGraphElement, TurtleShape}, + turtle_movement::TurtleStep, +}; + +pub(crate) fn run_animation_step( + commands: &mut Commands, + tcmd: &mut TurtleCommands, + turtle: &mut Query<&mut Animator, With>, +) { + loop { + match tcmd.get_next() { + Some(TurtleStep { + turtle_animation: Some(turtle_animation), + line_segment: Some(graph_element_to_draw), + line_animation: Some(line_animation), + }) => { + let mut turtle = turtle.single_mut(); + turtle.set_tweenable(turtle_animation); + match graph_element_to_draw { + TurtleGraphElement::TurtleLine(line) => { + commands.spawn_bundle(line).insert(line_animation); + } + TurtleGraphElement::Noop => (), + TurtleGraphElement::TurtleCircle(circle) => { + commands.spawn_bundle(circle).insert(line_animation); + } + } + return; + } + // In case a rotation is performed the line drawing can be skipped + Some(TurtleStep { + turtle_animation: Some(turtle_animation), + line_segment: Some(_), + line_animation: None, + }) => { + let mut turtle = turtle.single_mut(); + turtle.set_tweenable(turtle_animation); + return; + } + Some(_) => { + println!("without animation"); + } + None => { + println!("nothing to draw"); + return; + } + }; + } +} diff --git a/src/bin/circle.rs b/src/bin/circle.rs index 144b032..1334782 100644 --- a/src/bin/circle.rs +++ b/src/bin/circle.rs @@ -1,5 +1,3 @@ -use std::f32::consts::PI; - use bevy::prelude::*; use bevy_prototype_lyon::prelude::*; diff --git a/src/datatypes/angle.rs b/src/datatypes/angle.rs index b7b716b..10dfbc7 100644 --- a/src/datatypes/angle.rs +++ b/src/datatypes/angle.rs @@ -4,6 +4,8 @@ use std::{ ops::{Add, Div, Mul, Neg, Rem, Sub}, }; +use crate::turtle::Precision; + #[derive(Inspectable, Copy, Clone, Debug, PartialEq, Eq)] pub enum AngleUnit { Degrees(T), @@ -43,7 +45,7 @@ impl> Mul for Angle { } } -impl Angle { +impl Angle { pub fn limit_smaller_than_full_circle(self) -> Self { match self.value { AngleUnit::Degrees(v) => Self { diff --git a/src/datatypes/length.rs b/src/datatypes/length.rs index cf4fb9a..d410d76 100644 --- a/src/datatypes/length.rs +++ b/src/datatypes/length.rs @@ -1,4 +1,6 @@ use bevy_inspector_egui::Inspectable; +use crate::turtle::Precision; + #[derive(Inspectable, Default, Copy, Clone, Debug)] -pub struct Length(pub f32); +pub struct Length(pub Precision); diff --git a/src/main.rs b/src/main.rs index 05db5c4..66957fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ +mod animation_step; mod datatypes; mod debug; mod paths; mod primitives; mod turtle; mod turtle_movement; -mod turtle_shapes; use bevy::{prelude::*, window::close_on_esc}; use bevy_prototype_lyon::prelude::*; diff --git a/src/primitives.rs b/src/primitives.rs index c9fdf5a..b5ac94d 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -2,3 +2,4 @@ pub mod animation; pub mod bundles; pub mod components; pub mod turtle_primitives; +pub mod turtle_shapes; diff --git a/src/primitives/animation.rs b/src/primitives/animation.rs index e2fccdb..7c76837 100644 --- a/src/primitives/animation.rs +++ b/src/primitives/animation.rs @@ -5,7 +5,7 @@ use bevy_prototype_lyon::{ }; use bevy_tweening::Lens; -use crate::datatypes::angle::Angle; +use crate::{datatypes::angle::Angle, turtle::Precision}; pub(crate) struct LineAnimationLens { start: Vec2, @@ -29,8 +29,8 @@ pub(crate) struct CircleAnimationLens { pub start_pos: Vec2, pub center: Vec2, pub radii: Vec2, - pub start: Angle, - pub end: Angle, + pub start: Angle, + pub end: Angle, } impl Lens for CircleAnimationLens { @@ -54,7 +54,7 @@ impl Lens for CircleAnimationLens { pub(crate) struct CircleMovementLens { pub center: Vec2, pub start: Transform, - pub end: Angle, + pub end: Angle, } impl Lens for CircleMovementLens { diff --git a/src/primitives/bundles.rs b/src/primitives/bundles.rs index 642e6dd..1a593a6 100644 --- a/src/primitives/bundles.rs +++ b/src/primitives/bundles.rs @@ -1,4 +1,5 @@ use bevy::prelude::{Bundle, Color, Component, Name, Transform, Vec2}; +use bevy_inspector_egui::Inspectable; use bevy_prototype_lyon::{ entity::ShapeBundle, prelude::{DrawMode, FillMode, GeometryBuilder, PathBuilder, StrokeMode}, @@ -7,18 +8,21 @@ use bevy_prototype_lyon::{ use crate::{ datatypes::angle::Angle, - turtle::{Colors, TurtleCommand, TurtleCommands}, + turtle::{Colors, Precision, TurtleCommand, TurtleCommands}, }; -#[derive(Bundle)] -pub(crate) struct TurtleDrawLine { +use super::turtle_shapes; + +#[derive(Bundle, Inspectable, Default)] +pub struct TurtleDrawLine { #[bundle] + #[inspectable(ignore)] line: ShapeBundle, name: Name, marker: LineMarker, } -#[derive(Component, Default)] +#[derive(Component, Default, Inspectable)] struct LineMarker; impl TurtleDrawLine { @@ -38,23 +42,24 @@ impl TurtleDrawLine { } } -#[derive(Bundle)] +#[derive(Bundle, Inspectable, Default)] -pub(crate) struct TurtleDrawCircle { +pub struct TurtleDrawCircle { #[bundle] + #[inspectable(ignore)] line: ShapeBundle, name: Name, marker: CircleMarker, } -#[derive(Component, Default)] +#[derive(Component, Default, Inspectable)] struct CircleMarker; impl TurtleDrawCircle { pub(crate) fn new( center: Vec2, radii: Vec2, - angle: Angle, + angle: Angle, index: u64, start: Vec2, end: Vec2, @@ -89,6 +94,8 @@ pub struct Turtle { colors: Colors, commands: TurtleCommands, name: Name, + #[bundle] + shape: ShapeBundle, } impl Default for Turtle { @@ -97,6 +104,14 @@ impl Default for Turtle { colors: Colors::default(), commands: TurtleCommands::new(vec![]), name: Name::new("Turtle"), + shape: 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(), + ), } } } diff --git a/src/turtle_shapes.rs b/src/primitives/turtle_shapes.rs similarity index 50% rename from src/turtle_shapes.rs rename to src/primitives/turtle_shapes.rs index 45fc2b7..c87b6ee 100644 --- a/src/turtle_shapes.rs +++ b/src/primitives/turtle_shapes.rs @@ -3,31 +3,33 @@ use std::f32::consts::PI; use bevy::prelude::Vec2; use bevy_prototype_lyon::prelude::{Path, PathBuilder}; +use crate::turtle::Precision; + pub fn turtle() -> Path { - let polygon = &[ - [-2.5f32, 14.0f32], - [-1.25f32, 10.0f32], - [-4.0f32, 7.0f32], - [-7.0f32, 9.0f32], - [-9.0f32, 8.0f32], - [-6.0f32, 5.0f32], - [-7.0f32, 1.0f32], - [-5.0f32, -3.0f32], - [-8.0f32, -6.0f32], - [-6.0f32, -8.0f32], - [-4.0f32, -5.0f32], - [0.0f32, -7.0f32], - [4.0f32, -5.0f32], - [6.0f32, -8.0f32], - [8.0f32, -6.0f32], - [5.0f32, -3.0f32], - [7.0f32, 1.0f32], - [6.0f32, 5.0f32], - [9.0f32, 8.0f32], - [7.0f32, 9.0f32], - [4.0f32, 7.0f32], - [1.25f32, 10.0f32], - [2.5f32, 14.0f32], + let polygon: &[[Precision; 2]; 23] = &[ + [-2.5, 14.0], + [-1.25, 10.0], + [-4.0, 7.0], + [-7.0, 9.0], + [-9.0, 8.0], + [-6.0, 5.0], + [-7.0, 1.0], + [-5.0, -3.0], + [-8.0, -6.0], + [-6.0, -8.0], + [-4.0, -5.0], + [0.0, -7.0], + [4.0, -5.0], + [6.0, -8.0], + [8.0, -6.0], + [5.0, -3.0], + [7.0, 1.0], + [6.0, 5.0], + [9.0, 8.0], + [7.0, 9.0], + [4.0, 7.0], + [1.25, 10.0], + [2.5, 14.0], ]; let mut turtle_path = PathBuilder::new(); turtle_path.line_to(Vec2::new(1.0, 1.0)); diff --git a/src/turtle.rs b/src/turtle.rs index c6220f6..985f1f0 100644 --- a/src/turtle.rs +++ b/src/turtle.rs @@ -11,12 +11,10 @@ use bevy_tweening::{ #[allow(unused_imports)] use crate::paths::{circle_star::circle_star, geometry_task::geometry_task}; use crate::{ + animation_step::run_animation_step, datatypes::{angle::Angle, length::Length}, - primitives::{ - animation::{CircleAnimationLens, LineAnimationLens}, - bundles::{Turtle, TurtleDrawCircle, TurtleDrawLine}, - }, - turtle_shapes, + primitives::bundles::{Turtle, TurtleDrawCircle, TurtleDrawLine}, + turtle_movement::TurtleStep, }; pub struct TurtlePlugin; @@ -66,7 +64,7 @@ impl TurtleCommands { } impl TurtleCommands { - fn get_next(&mut self) -> Option<(Option>, Option)> { + pub(crate) fn get_next(&mut self) -> Option { let index = self.state.index; let next_index = index + 1; @@ -86,11 +84,21 @@ impl TurtleCommands { } TurtleCommand::PenUp => { self.state.drawing = false; - (None, None) + + TurtleStep { + turtle_animation: None, + line_segment: None, + line_animation: None, + } } TurtleCommand::PenDown => { self.state.drawing = true; - (None, None) + + TurtleStep { + turtle_animation: None, + line_segment: None, + line_animation: None, + } } TurtleCommand::Circle { radius, angle } => { crate::turtle_movement::turtle_circle(&mut self.state, radius.0 as f32, *angle) @@ -107,17 +115,8 @@ impl TurtleCommands { #[derive(Inspectable, Default)] pub enum TurtleGraphElement { - TurtleLine { - start: Vec2, - end: Vec2, - }, - TurtleCircle { - start: Vec2, - end: Vec2, - center: Vec2, - radii: Vec2, - angle: Angle, - }, + TurtleLine(TurtleDrawLine), + TurtleCircle(TurtleDrawCircle), #[default] Noop, } @@ -159,17 +158,9 @@ fn setup(mut commands: Commands) { )); commands.spawn_bundle(Camera2dBundle::default()); let mut turtle_bundle = Turtle::default(); - turtle_bundle.set_commands(geometry_task()); + turtle_bundle.set_commands(circle_star()); 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); } @@ -182,117 +173,26 @@ fn draw_lines( ) { 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; - } - }; - } + run_animation_step(&mut commands, &mut tcmd, &mut turtle) } } fn keypresses( mut commands: Commands, keys: Res>, - mut qry: Query<&mut Animator, With>, mut tcmd: Query<&mut TurtleCommands>, + mut turtle: Query<&mut Animator, With>, ) { if keys.just_pressed(KeyCode::W) { let mut tcmd = tcmd.single_mut(); tcmd.state = TurtleState { start: Vec2::ZERO, heading: Angle::degrees(0.), - speed: 2000, + speed: 500, 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, - )); - } - } - }; + + run_animation_step(&mut commands, &mut tcmd, &mut turtle); } } diff --git a/src/turtle_movement.rs b/src/turtle_movement.rs index bbc2279..4a64569 100644 --- a/src/turtle_movement.rs +++ b/src/turtle_movement.rs @@ -1,78 +1,89 @@ use std::time::Duration; use bevy::prelude::{Quat, Transform, Vec2, Vec3}; +use bevy_prototype_lyon::prelude::Path; use bevy_tweening::{ lens::{TransformPositionLens, TransformRotateZLens}, - EaseFunction, Tween, TweeningType, + Animator, EaseFunction, Tween, TweeningType, }; use crate::{ datatypes::angle::Angle, - primitives::animation::CircleMovementLens, - turtle::{TurtleGraphElement, TurtleState}, + primitives::{ + animation::{CircleAnimationLens, CircleMovementLens, LineAnimationLens}, + bundles::{TurtleDrawCircle, TurtleDrawLine}, + }, + turtle::{Precision, TurtleGraphElement, TurtleState}, }; -pub fn turtle_turn( - state: &mut TurtleState, - angle_to_turn: Angle, -) -> (Option>, Option) { +pub struct TurtleStep { + pub turtle_animation: Option>, + pub line_segment: Option, + pub line_animation: Option>, +} + +pub fn turtle_turn(state: &mut TurtleState, angle_to_turn: Angle) -> TurtleStep { let start = state.heading; let end = state.heading + angle_to_turn; let animation = Tween::new( - // Use a quadratic easing on both endpoints EaseFunction::QuadraticInOut, TweeningType::Once, - // Animation time Duration::from_millis(state.speed), - // Rotate the turtle TransformRotateZLens { start: start.to_radians().value(), end: end.to_radians().value(), }, ) .with_completed_event(state.index as u64); - // Dont move and draw + // Don't draw as the position does not change let line = TurtleGraphElement::Noop; // Update the state state.heading = end.limit_smaller_than_full_circle(); - (Some(animation), Some(line)) + TurtleStep { + turtle_animation: Some(animation), + line_segment: Some(line), + line_animation: None, + } } -pub fn turtle_move( - state: &mut TurtleState, - length: f32, -) -> (Option>, Option) { +pub fn turtle_move(state: &mut TurtleState, length: Precision) -> TurtleStep { let start = state.start; let end = state.start + (Vec2::from_angle(state.heading.to_radians().value()) * length); let turtle_movement_animation = Tween::new( - // accelerate and decelerate EaseFunction::QuadraticInOut, TweeningType::Once, - // later to be controlled by speed Duration::from_millis(state.speed), - // set the start and end of the animation TransformPositionLens { start: start.extend(0.), end: end.extend(0.), }, ) .with_completed_event(state.index as u64); - // The line for animating and drawing let line = if state.drawing { - TurtleGraphElement::TurtleLine { start, end } + TurtleGraphElement::TurtleLine(TurtleDrawLine::new(start, end, state.index)) } else { TurtleGraphElement::Noop }; + let line_animator = Animator::new(Tween::new( + EaseFunction::QuadraticInOut, + TweeningType::Once, + Duration::from_millis(state.speed), + LineAnimationLens::new(start, end), + )); state.start = end; - (Some(turtle_movement_animation), Some(line)) + TurtleStep { + turtle_animation: Some(turtle_movement_animation), + line_segment: Some(line), + line_animation: Some(line_animator), + } } pub fn turtle_circle( state: &mut TurtleState, - radius: f32, - angle: Angle, -) -> (Option>, Option) { - let radius_tuple = Vec2::ONE * radius.abs(); + radius: Precision, + angle: Angle, +) -> TurtleStep { + let radii = Vec2::ONE * radius.abs(); let left_right = Angle::degrees(if radius >= 0. { 90. } else { -90. }); let center = state.start + (Vec2::new(radius.abs(), 0.).rotate(Vec2::from_angle( @@ -80,12 +91,9 @@ pub fn turtle_circle( ))); let turtle_movement_animation = Tween::new( - // accelerate and decelerate EaseFunction::QuadraticInOut, TweeningType::Once, - // later to be controlled by speed Duration::from_millis(state.speed), - // set the start and end of the animation CircleMovementLens { start: Transform { translation: state.start.extend(0.), @@ -97,23 +105,39 @@ pub fn turtle_circle( }, ) .with_completed_event(state.index as u64); - // The line for animating and drawing - let line = if state.drawing { - TurtleGraphElement::TurtleCircle { - center, - radii: radius_tuple, - angle, - start: state.start, - end: state.start, - } - } else { - TurtleGraphElement::Noop - }; let end_pos = center + Vec2::new(radius.abs(), 0.).rotate(Vec2::from_angle( (state.heading + angle - left_right).to_radians().value(), )); + let line = if state.drawing { + TurtleGraphElement::TurtleCircle(TurtleDrawCircle::new( + center, + radii, + Angle::degrees(0.), + state.index, + state.start, + end_pos, + )) + } else { + TurtleGraphElement::Noop + }; + let line_animator = Animator::new(Tween::new( + EaseFunction::QuadraticInOut, + TweeningType::Once, + Duration::from_millis(state.speed), + CircleAnimationLens { + start_pos: state.start, + center, + radii, + start: Angle::degrees(0.), + end: angle, + }, + )); state.start = end_pos; state.heading = state.heading + angle; - (Some(turtle_movement_animation), Some(line)) + TurtleStep { + turtle_animation: Some(turtle_movement_animation), + line_segment: Some(line), + line_animation: Some(line_animator), + } }