turtlers/src/turtle.rs
2022-08-23 10:52:20 +02:00

299 lines
9.7 KiB
Rust

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::<Path>)
.register_inspectable::<Colors>()
.register_inspectable::<TurtleCommands>();
}
}
pub type Precision = f32;
#[derive(Component, Inspectable)]
pub struct TurtleCommands {
commands: Vec<TurtleCommand>,
lines: Vec<TurtleGraphElement>,
state: TurtleState,
}
#[derive(Inspectable)]
pub struct TurtleState {
pub start: Vec2,
pub heading: Angle<f32>,
pub speed: u64,
pub index: u64,
pub drawing: bool,
}
impl TurtleCommands {
pub fn new(commands: Vec<TurtleCommand>) -> 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<Tween<Transform>>, Option<TurtleGraphElement>)> {
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<f32>,
},
#[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<f32>),
Right(Angle<f32>),
PenUp,
PenDown,
#[default]
Pause,
Circle {
radius: Length,
angle: Angle<f32>,
},
}
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<Transform>, With<TurtleShape>>,
mut query_event: EventReader<TweenCompleted>, // 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<Input<KeyCode>>,
mut qry: Query<&mut Animator<Transform>, With<TurtleShape>>,
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,
));
}
}
};
}
}