Compare commits

..

3 Commits

Author SHA1 Message Date
8fb0022280
fix the testcases 2023-09-03 22:57:51 +02:00
9a551573cb
bump versions to bevy 0.11 2023-09-03 22:41:37 +02:00
03af02b4e6
experiment WIP 2022-11-21 06:54:46 +01:00
20 changed files with 2291 additions and 1285 deletions

2693
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,12 @@ default-run = "turtlers"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
bevy = { version = "0.8", features = ["dynamic"] } bevy = { version = "0.11", features = ["dynamic_linking", "wayland"] }
bevy-inspector-egui = "0.12" bevy-inspector-egui = "0.19"
bevy_prototype_lyon = "0.6" bevy_egui = "0.21"
bevy_tweening = "0.5" egui = "0.22"
bevy_prototype_lyon = {version="0.9"}
bevy_tweening = {version="0.8"}
num-traits = "0.2" num-traits = "0.2"
# Enable a small amount of optimization in debug mode # Enable a small amount of optimization in debug mode

View File

@ -1 +1 @@
*[x] make the *[ ] Minimal example of filled animation

View File

@ -1,4 +1,5 @@
use bevy::prelude::{Commands, Query, Transform, With}; use bevy::prelude::{Color, Commands, Query, Transform, With};
use bevy_prototype_lyon::prelude::{Fill, Stroke};
use bevy_tweening::Animator; use bevy_tweening::Animator;
use crate::{ use crate::{
@ -17,16 +18,20 @@ pub(crate) fn run_animation_step(
turtle_animation: Some(turtle_animation), turtle_animation: Some(turtle_animation),
line_segment: Some(graph_element_to_draw), line_segment: Some(graph_element_to_draw),
line_animation: Some(line_animation), line_animation: Some(line_animation),
fill,
stroke,
}) => { }) => {
let mut turtle = turtle.single_mut(); let mut turtle = turtle.single_mut();
turtle.set_tweenable(turtle_animation); turtle.set_tweenable(turtle_animation);
let fill = fill.unwrap_or(Fill::color(Color::MIDNIGHT_BLUE));
let stroke = stroke.unwrap_or(Stroke::color(Color::BLACK));
match graph_element_to_draw { match graph_element_to_draw {
TurtleGraphElement::TurtleLine(line) => { TurtleGraphElement::TurtleLine(line) => {
commands.spawn_bundle(line).insert(line_animation); commands.spawn((line, line_animation, fill, stroke));
} }
TurtleGraphElement::Noop => (), TurtleGraphElement::Noop => (),
TurtleGraphElement::TurtleCircle(circle) => { TurtleGraphElement::TurtleCircle(circle) => {
commands.spawn_bundle(circle).insert(line_animation); commands.spawn((circle, line_animation, fill, stroke));
} }
} }
return; return;
@ -36,6 +41,7 @@ pub(crate) fn run_animation_step(
turtle_animation: Some(turtle_animation), turtle_animation: Some(turtle_animation),
line_segment: Some(_), line_segment: Some(_),
line_animation: None, line_animation: None,
..
}) => { }) => {
let mut turtle = turtle.single_mut(); let mut turtle = turtle.single_mut();
turtle.set_tweenable(turtle_animation); turtle.set_tweenable(turtle_animation);

View File

@ -1,143 +0,0 @@
//! Create and play an animation defined by code that operates on the `Transform` component.
use std::f32::consts::{FRAC_PI_2, PI};
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 1.0,
})
.add_startup_system(setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut animations: ResMut<Assets<AnimationClip>>,
) {
// Camera
commands.spawn_bundle(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
// The animation API uses the `Name` component to target entities
let planet = Name::new("planet");
let orbit_controller = Name::new("orbit_controller");
let satellite = Name::new("satellite");
// Creating the animation
let mut animation = AnimationClip::default();
// A curve can modify a single part of a transform, here the translation
animation.add_curve_to_path(
EntityPath {
parts: vec![planet.clone()],
},
VariableCurve {
keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0],
keyframes: Keyframes::Translation(vec![
Vec3::new(1.0, 0.0, 1.0),
Vec3::new(-1.0, 0.0, 1.0),
Vec3::new(-1.0, 0.0, -1.0),
Vec3::new(1.0, 0.0, -1.0),
// in case seamless looping is wanted, the last keyframe should
// be the same as the first one
Vec3::new(1.0, 0.0, 1.0),
]),
},
);
// Or it can modify the rotation of the transform.
// To find the entity to modify, the hierarchy will be traversed looking for
// an entity with the right name at each level
animation.add_curve_to_path(
EntityPath {
parts: vec![planet.clone(), orbit_controller.clone()],
},
VariableCurve {
keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0],
keyframes: Keyframes::Rotation(vec![
Quat::from_axis_angle(Vec3::Y, 0.0),
Quat::from_axis_angle(Vec3::Y, FRAC_PI_2),
Quat::from_axis_angle(Vec3::Y, PI),
Quat::from_axis_angle(Vec3::Y, 3.0 * FRAC_PI_2),
Quat::from_axis_angle(Vec3::Y, 0.0),
]),
},
);
// If a curve in an animation is shorter than the other, it will not repeat
// until all other curves are finished. In that case, another animation should
// be created for each part that would have a different duration / period
animation.add_curve_to_path(
EntityPath {
parts: vec![planet.clone(), orbit_controller.clone(), satellite.clone()],
},
VariableCurve {
keyframe_timestamps: vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0],
keyframes: Keyframes::Scale(vec![
Vec3::splat(0.8),
Vec3::splat(1.2),
Vec3::splat(0.8),
Vec3::splat(1.2),
Vec3::splat(0.8),
Vec3::splat(1.2),
Vec3::splat(0.8),
Vec3::splat(1.2),
Vec3::splat(0.8),
]),
},
);
// There can be more than one curve targeting the same entity path
animation.add_curve_to_path(
EntityPath {
parts: vec![planet.clone(), orbit_controller.clone(), satellite.clone()],
},
VariableCurve {
keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0],
keyframes: Keyframes::Rotation(vec![
Quat::from_axis_angle(Vec3::Y, 0.0),
Quat::from_axis_angle(Vec3::Y, FRAC_PI_2),
Quat::from_axis_angle(Vec3::Y, PI),
Quat::from_axis_angle(Vec3::Y, 3.0 * FRAC_PI_2),
Quat::from_axis_angle(Vec3::Y, 0.0),
]),
},
);
// Create the animation player, and set it to repeat
let mut player = AnimationPlayer::default();
player.play(animations.add(animation)).repeat();
// Create the scene that will be animated
// First entity is the planet
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Icosphere::default())),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
..default()
})
// Add the Name component, and the animation player
.insert_bundle((planet, player))
.with_children(|p| {
// This entity is just used for animation, but doesn't display anything
p.spawn_bundle(SpatialBundle::default())
// Add the Name component
.insert(orbit_controller)
.with_children(|p| {
// The satellite, placed at a distance of the planet
p.spawn_bundle(PbrBundle {
transform: Transform::from_xyz(1.5, 0.0, 0.0),
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
material: materials.add(Color::rgb(0.3, 0.9, 0.3).into()),
..default()
})
// Add the Name component
.insert(satellite);
});
});
}

112
src/bin/bevy_egui_test.rs Normal file
View File

@ -0,0 +1,112 @@
use bevy::{app::AppExit, prelude::*};
use bevy_egui::{egui, EguiContexts, EguiPlugin};
use bevy_prototype_lyon::prelude::{
Fill, GeometryBuilder, Path, PathBuilder, ShapeBundle, ShapePlugin, Stroke,
};
#[derive(Default, Resource)]
struct OccupiedScreenSpace {
left: f32,
top: f32,
right: f32,
bottom: f32,
}
#[derive(Default, Resource)]
struct Line {
x: f32,
y: f32,
}
#[derive(Resource, Deref, DerefMut)]
struct OriginalCameraTransform(Transform);
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: (800., 600.).into(),
title: "Turtle Window".to_string(),
present_mode: bevy::window::PresentMode::AutoVsync,
//decorations: false,
..default()
}),
..default()
}))
.add_plugins(EguiPlugin)
.add_plugins(ShapePlugin)
.init_resource::<OccupiedScreenSpace>()
.add_systems(Startup, setup_system)
.add_systems(Update, ui)
.add_systems(Update, update_path)
.run();
}
fn update_path(line: Res<Line>, mut path: Query<&mut Path>) {
for mut path in path.iter_mut() {
let mut path_builder = PathBuilder::new();
path_builder.move_to(Vec2::new(line.x, line.y));
path_builder.line_to(Vec2::new(100., 0.));
*path = path_builder.build();
}
}
fn ui(
mut egui_contexts: EguiContexts,
mut occupied_screen_space: ResMut<OccupiedScreenSpace>,
mut line: ResMut<Line>,
mut exit: EventWriter<AppExit>,
) {
let mut style = (*egui_contexts.ctx_mut().style()).clone();
style.visuals.button_frame = false;
egui_contexts.ctx_mut().set_style(style);
occupied_screen_space.left = 0.0;
occupied_screen_space.right = egui::SidePanel::right("right_panel")
.resizable(false)
.show(egui_contexts.ctx_mut(), |ui| {
ui.add_space(7.);
ui.menu_button("Menu", |ui| {
if ui
.add_sized([ui.available_width(), 30.], egui::Button::new("Exit"))
.clicked()
{
exit.send(AppExit);
}
})
})
.response
.rect
.width();
occupied_screen_space.top = 0.0;
occupied_screen_space.bottom = egui::TopBottomPanel::bottom("bottom_panel")
.resizable(false)
.show(egui_contexts.ctx_mut(), |ui| {
ui.add_sized(
ui.available_size(),
egui::Slider::new(&mut line.x, 0.0..=100.0),
);
})
.response
.rect
.height();
}
fn setup_system(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
commands.insert_resource(Line { x: 100., y: 100. });
let mut path_builder = PathBuilder::new();
path_builder.move_to(Vec2::new(200., 200.));
path_builder.line_to(Vec2::new(100., 0.));
let line = path_builder.build();
let fill = Fill::color(Color::MIDNIGHT_BLUE);
let stroke = Stroke::color(Color::BLACK);
commands.spawn((
ShapeBundle {
path: GeometryBuilder::build_as(&line),
..default()
},
fill,
stroke,
));
}

View File

@ -1,30 +1,142 @@
use std::time::Duration;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_prototype_lyon::prelude::*; use bevy_prototype_lyon::prelude::*;
use bevy_tweening::{
component_animator_system, Animator, EaseFunction, Lens, Sequence, Tween, TweeningPlugin,
};
fn main() { fn main() {
App::new() App::new()
.insert_resource(Msaa { samples: 4 }) .insert_resource(Msaa::Sample4)
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_plugin(ShapePlugin) .add_plugins(ShapePlugin)
.add_startup_system(setup_system) .add_plugins(TweeningPlugin)
.add_systems(Startup, setup_system)
.add_systems(Update, component_animator_system::<Path>)
.run(); .run();
} }
fn setup_system(mut commands: Commands) { fn setup_system(mut commands: Commands) {
let mut path_builder = PathBuilder::new(); let mut path_builder = PathBuilder::new();
path_builder.move_to(Vec2::new(100., 0.)); path_builder.line_to(Vec2::new(100., 0.));
path_builder.arc( let ops = vec![
100.0 * Vec2::ZERO, GElem::Circle {
100.0 * Vec2::ONE, center: Vec2::ZERO,
90f32.to_radians(), radii: Vec2::splat(100.),
0., angle: 90.,
); },
GElem::Line {
start: Vec2::new(0., 100.),
target: Vec2::splat(200.),
},
GElem::Line {
start: Vec2::splat(200.),
target: Vec2::new(100., 0.),
},
GElem::Circle {
center: Vec2::ZERO,
radii: Vec2::splat(100.),
angle: -90.,
},
GElem::Line {
start: Vec2::new(0., -100.),
target: Vec2::new(200., -200.),
},
GElem::Line {
start: Vec2::new(200., -200.),
target: Vec2::new(100., 0.),
},
];
let line = path_builder.build(); let line = path_builder.build();
commands.spawn_bundle(Camera2dBundle::default()); commands.spawn(Camera2dBundle::default());
commands.spawn_bundle(GeometryBuilder::build_as( let mut seq = Sequence::with_capacity(ops.len());
&line, for (step, op) in ops.clone().into_iter().enumerate() {
DrawMode::Stroke(StrokeMode::new(Color::BLACK, 10.0)), let mut done = ops.clone();
Transform::default(), done.truncate(step);
)); let next = Tween::new(
EaseFunction::QuadraticInOut,
Duration::from_millis(1000),
FilledAnimation { current: op, done },
);
seq = seq.then(next);
}
let animator = Animator::new(seq);
let fill = Fill::color(Color::MIDNIGHT_BLUE);
let stroke = Stroke::color(Color::BLACK);
commands
.spawn((
ShapeBundle {
path: GeometryBuilder::build_as(&line),
..default()
},
fill,
stroke,
))
.insert(animator);
}
#[derive(Clone, Copy)]
enum GElem {
Circle {
center: Vec2,
radii: Vec2,
angle: f32,
},
Line {
start: Vec2,
target: Vec2,
},
}
impl GElem {
fn draw_to_builder(self, b: &mut PathBuilder) {
match self {
GElem::Circle {
center,
radii,
angle,
} => {
b.arc(center, radii, angle.to_radians(), 0.);
}
GElem::Line { target, start: _ } => {
b.line_to(target);
}
}
}
}
struct FilledAnimation {
current: GElem,
done: Vec<GElem>,
}
impl Lens<Path> for FilledAnimation {
fn lerp(&mut self, target: &mut Path, ratio: f32) {
let mut path_builder = PathBuilder::new();
path_builder.move_to(Vec2::new(100., 0.));
for x in &self.done {
x.draw_to_builder(&mut path_builder)
}
let part = match self.current {
GElem::Circle {
center,
radii,
angle,
} => GElem::Circle {
center,
radii,
angle: angle * ratio,
},
GElem::Line { target, start } => GElem::Line {
target: start + ((target - start) * ratio),
start,
},
};
part.draw_to_builder(&mut path_builder);
*target = path_builder.build();
}
} }

View File

@ -1,2 +1,8 @@
use bevy::prelude::Vec2;
pub mod angle; pub mod angle;
pub mod length; pub mod length;
pub type Coordinate = Vec2;
pub type Visibility = bool;
pub type Speed = u32;

View File

@ -1,12 +1,14 @@
use bevy_inspector_egui::Inspectable;
use std::{ use std::{
f32::consts::PI, f32::consts::PI,
ops::{Add, Div, Mul, Neg, Rem, Sub}, ops::{Add, Div, Mul, Neg, Rem, Sub},
}; };
use bevy::reflect::Reflect;
use crate::turtle::Precision; use crate::turtle::Precision;
#[derive(Inspectable, Copy, Clone, Debug, PartialEq, Eq)] #[derive(Reflect, Copy, Clone, Debug, PartialEq, Eq)]
pub enum AngleUnit<T: Default> { pub enum AngleUnit<T: Default> {
Degrees(T), Degrees(T),
Radians(T), Radians(T),
@ -18,7 +20,7 @@ impl<T: Default> Default for AngleUnit<T> {
} }
} }
#[derive(Inspectable, Copy, Default, Clone, Debug, PartialEq, Eq)] #[derive(Reflect, Copy, Default, Clone, Debug, PartialEq, Eq)]
pub struct Angle<T: Default> { pub struct Angle<T: Default> {
value: AngleUnit<T>, value: AngleUnit<T>,
} }
@ -177,3 +179,19 @@ fn convert_to_radians() {
let converted = degr.to_radians(); let converted = degr.to_radians();
assert_eq!(radi, converted) assert_eq!(radi, converted)
} }
#[test]
fn sum_degrees() {
let fst = Angle::degrees(30f32);
let snd = Angle::degrees(30f32);
let sum = fst + snd;
assert!((sum.value() - 60f32).abs() < 0.0001);
assert!((sum.to_radians().value() - 60f32.to_radians()).abs() < 0.0001);
}
#[test]
fn sum_mixed() {
let fst = Angle::degrees(30f32);
let snd = Angle::radians(30f32.to_radians());
let sum = fst + snd;
assert!((sum.to_degrees().value() - 60f32).abs() < 0.0001);
assert!((sum.to_radians().value() - 60f32.to_radians()).abs() < 0.0001);
}

View File

@ -1,6 +1,6 @@
use bevy_inspector_egui::Inspectable; use bevy::reflect::Reflect;
use crate::turtle::Precision; use crate::turtle::Precision;
#[derive(Inspectable, Default, Copy, Clone, Debug)] #[derive(Reflect, Default, Copy, Clone, Debug)]
pub struct Length(pub Precision); pub struct Length(pub Precision);

View File

@ -1,5 +1,5 @@
use bevy::prelude::Plugin; use bevy::prelude::Plugin;
use bevy_inspector_egui::WorldInspectorPlugin; use bevy_inspector_egui::quick::WorldInspectorPlugin;
pub struct DebugPlugin; pub struct DebugPlugin;

View File

@ -3,8 +3,10 @@ mod datatypes;
mod debug; mod debug;
mod paths; mod paths;
mod primitives; mod primitives;
mod structs;
mod turtle; mod turtle;
mod turtle_movement; mod turtle_movement;
mod turtle_state;
use bevy::{prelude::*, window::close_on_esc}; use bevy::{prelude::*, window::close_on_esc};
use bevy_prototype_lyon::prelude::*; use bevy_prototype_lyon::prelude::*;
@ -12,16 +14,17 @@ use turtle::TurtlePlugin;
fn main() { fn main() {
App::new() App::new()
.insert_resource(Msaa { samples: 4 }) .insert_resource(Msaa::Sample4)
.insert_resource(ClearColor(Color::BEIGE)) .insert_resource(ClearColor(Color::BEIGE))
.insert_resource(WindowDescriptor { .add_plugins(DefaultPlugins.set(WindowPlugin {
width: 500.0, primary_window: Some(Window {
height: 500.0, resolution: (800.,600.).into(),
title: "Turtle Window".to_string(), //title: "Turtle Window".to_string(),
present_mode: bevy::window::PresentMode::AutoVsync, present_mode: bevy::window::PresentMode::AutoVsync,
..default()
}),
..default() ..default()
}) }))
.add_plugins(DefaultPlugins)
.add_plugin(ShapePlugin) .add_plugin(ShapePlugin)
.add_plugin(debug::DebugPlugin) .add_plugin(debug::DebugPlugin)
.add_plugin(TurtlePlugin) .add_plugin(TurtlePlugin)

View File

@ -1,8 +1,10 @@
use bevy::prelude::{Bundle, Color, Component, Name, Transform, Vec2}; use bevy::{
use bevy_inspector_egui::Inspectable; prelude::{default, Bundle, Component, Name, Vec2},
reflect::Reflect,
};
use bevy_prototype_lyon::{ use bevy_prototype_lyon::{
entity::ShapeBundle, entity::ShapeBundle,
prelude::{DrawMode, FillMode, GeometryBuilder, PathBuilder, StrokeMode}, prelude::{Fill, GeometryBuilder, PathBuilder, Stroke},
shapes::Line, shapes::Line,
}; };
@ -13,46 +15,41 @@ use crate::{
use super::turtle_shapes; use super::turtle_shapes;
#[derive(Bundle, Inspectable, Default)] #[derive(Bundle, Reflect, Default)]
pub struct TurtleDrawLine { pub struct TurtleDrawLine {
#[bundle] #[reflect(ignore)]
#[inspectable(ignore)]
line: ShapeBundle, line: ShapeBundle,
name: Name, name: Name,
marker: LineMarker, marker: LineMarker,
} }
#[derive(Component, Default, Inspectable)] #[derive(Component, Default, Reflect)]
struct LineMarker; struct LineMarker;
impl TurtleDrawLine { impl TurtleDrawLine {
pub(crate) fn new(start: Vec2, _end: Vec2, index: u64) -> Self { pub(crate) fn new(start: Vec2, _end: Vec2, index: u64) -> Self {
let bundle = ShapeBundle {
path: GeometryBuilder::build_as(&Line(start, start)),
..default()
};
Self { Self {
line: GeometryBuilder::build_as( line: bundle,
&Line(start, start),
DrawMode::Outlined {
fill_mode: FillMode::color(Color::MIDNIGHT_BLUE),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Transform::identity(),
),
name: Name::new(format!("Line {}", index)), name: Name::new(format!("Line {}", index)),
marker: LineMarker, marker: LineMarker,
} }
} }
} }
#[derive(Bundle, Inspectable, Default)] #[derive(Bundle, Reflect, Default)]
pub struct TurtleDrawCircle { pub struct TurtleDrawCircle {
#[bundle] #[reflect(ignore)]
#[inspectable(ignore)]
line: ShapeBundle, line: ShapeBundle,
name: Name, name: Name,
marker: CircleMarker, marker: CircleMarker,
} }
#[derive(Component, Default, Inspectable)] #[derive(Component, Default, Reflect)]
struct CircleMarker; struct CircleMarker;
impl TurtleDrawCircle { impl TurtleDrawCircle {
@ -74,15 +71,12 @@ impl TurtleDrawCircle {
let line = path_builder.build(); let line = path_builder.build();
println!("Draw Circle: {} {} {:?}", center, radii, angle); println!("Draw Circle: {} {} {:?}", center, radii, angle);
let bundle = ShapeBundle {
path: GeometryBuilder::build_as(&line),
..default()
};
Self { Self {
line: GeometryBuilder::build_as( line: bundle,
&line,
DrawMode::Outlined {
fill_mode: FillMode::color(Color::rgba(0., 0., 0., 0.)),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Transform::identity(),
),
name: Name::new(format!("Circle {}", index)), name: Name::new(format!("Circle {}", index)),
marker: CircleMarker, marker: CircleMarker,
} }
@ -94,24 +88,20 @@ pub struct Turtle {
colors: Colors, colors: Colors,
commands: TurtleCommands, commands: TurtleCommands,
name: Name, name: Name,
#[bundle]
shape: ShapeBundle, shape: ShapeBundle,
} }
impl Default for Turtle { impl Default for Turtle {
fn default() -> Self { fn default() -> Self {
let bundle = ShapeBundle {
path: GeometryBuilder::build_as(&turtle_shapes::turtle()),
..default()
};
Self { Self {
colors: Colors::default(), colors: Colors::default(),
commands: TurtleCommands::new(vec![]), commands: TurtleCommands::new(vec![]),
name: Name::new("Turtle"), name: Name::new("Turtle"),
shape: GeometryBuilder::build_as( shape: bundle,
&turtle_shapes::turtle(),
DrawMode::Outlined {
fill_mode: FillMode::color(Color::MIDNIGHT_BLUE),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Transform::identity(),
),
} }
} }
} }

69
src/structs.rs Normal file
View File

@ -0,0 +1,69 @@
use bevy_prototype_lyon::prelude::PathBuilder;
use crate::{
datatypes::{
angle::Angle,
length::Length,
Coordinate,
},
turtle::Precision,
};
/**
* All the possibilities to draw something with turtle. All the commands can get the position, heading,
* color and fill_color from the turtles state.
*/
pub enum MoveCommand {
Forward(Length),
Backward(Length),
Circle { radius: Length, angle: Angle<f32> },
Goto(Coordinate),
Home,
}
/// Different ways to drop breadcrumbs on the way like a dot or a stamp of the turtles shape.
pub enum Breadcrumb {
Dot,
Stamp,
}
/// Different ways that change the orientation of the turtle.
pub enum OrientationCommand {
Left(Angle<Precision>),
Right(Angle<Precision>),
SetHeading,
LookAt(Coordinate),
}
/// A combination of all commands that can be used while drawing.
pub enum DrawElement {
Draw(MoveCommand),
Move(MoveCommand),
Orient(OrientationCommand),
Drip(Breadcrumb),
}
pub enum DrawingSegment {
Single(DrawElement),
Outline(Vec<DrawElement>),
Filled(Vec<DrawElement>),
}
impl DrawingSegment {
pub fn draw(&self) {
match self {
DrawingSegment::Single(elem) => {
let mut path_builder = PathBuilder::new();
}
DrawingSegment::Outline(_) => todo!(),
DrawingSegment::Filled(_) => todo!(),
}
}
}
#[derive(PartialEq, Eq)]
pub enum DrawState {
PenDown,
PenUp,
}

View File

@ -1,15 +1,17 @@
pub mod builders;
pub mod state;
pub mod turtle;
use std::time::Duration; use std::time::Duration;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_inspector_egui::{Inspectable, RegisterInspectable};
use bevy_prototype_lyon::prelude::*; use bevy_prototype_lyon::prelude::*;
use bevy_tweening::{ use bevy_tweening::{
component_animator_system, lens::TransformScaleLens, Animator, EaseFunction, Tween, component_animator_system, lens::TransformScaleLens, Animator, EaseFunction, RepeatCount,
TweenCompleted, TweeningPlugin, TweeningType, Tween, TweenCompleted, TweeningPlugin,
}; };
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::paths::{circle_star::circle_star, geometry_task::geometry_task}; use crate::paths;
use crate::{ use crate::{
animation_step::run_animation_step, animation_step::run_animation_step,
datatypes::{angle::Angle, length::Length}, datatypes::{angle::Angle, length::Length},
@ -26,19 +28,19 @@ impl Plugin for TurtlePlugin {
.add_system(keypresses) .add_system(keypresses)
.add_system(draw_lines) .add_system(draw_lines)
.add_system(component_animator_system::<Path>) .add_system(component_animator_system::<Path>)
.register_inspectable::<Colors>() .register_type::<Colors>()
.register_inspectable::<TurtleCommands>(); .register_type::<TurtleCommands>();
} }
} }
pub type Precision = f32; pub type Precision = f32;
#[derive(Component, Inspectable)] #[derive(Component, Reflect)]
pub struct TurtleCommands { pub struct TurtleCommands {
commands: Vec<TurtleCommand>, commands: Vec<TurtleCommand>,
lines: Vec<TurtleGraphElement>, lines: Vec<TurtleGraphElement>,
state: TurtleState, state: TurtleState,
} }
#[derive(Inspectable)] #[derive(Reflect)]
pub struct TurtleState { pub struct TurtleState {
pub start: Vec2, pub start: Vec2,
pub heading: Angle<f32>, pub heading: Angle<f32>,
@ -89,6 +91,8 @@ impl TurtleCommands {
turtle_animation: None, turtle_animation: None,
line_segment: None, line_segment: None,
line_animation: None, line_animation: None,
fill: None,
stroke: None,
} }
} }
TurtleCommand::PenDown => { TurtleCommand::PenDown => {
@ -98,12 +102,16 @@ impl TurtleCommands {
turtle_animation: None, turtle_animation: None,
line_segment: None, line_segment: None,
line_animation: None, line_animation: None,
fill: None,
stroke: None,
} }
} }
TurtleCommand::Circle { radius, angle } => { TurtleCommand::Circle { radius, angle } => {
crate::turtle_movement::turtle_circle(&mut self.state, radius.0 as f32, *angle) crate::turtle_movement::turtle_circle(&mut self.state, radius.0 as f32, *angle)
} }
TurtleCommand::Pause => todo!(), TurtleCommand::Pause => todo!(),
TurtleCommand::BeginFill => todo!(),
TurtleCommand::EndFill => todo!(),
}; };
self.state.index = next_index; self.state.index = next_index;
Some(res) Some(res)
@ -113,7 +121,7 @@ impl TurtleCommands {
} }
} }
#[derive(Inspectable, Default)] #[derive(Reflect, Default)]
pub enum TurtleGraphElement { pub enum TurtleGraphElement {
TurtleLine(TurtleDrawLine), TurtleLine(TurtleDrawLine),
TurtleCircle(TurtleDrawCircle), TurtleCircle(TurtleDrawCircle),
@ -121,16 +129,16 @@ pub enum TurtleGraphElement {
Noop, Noop,
} }
#[derive(Clone, Component, Inspectable)] #[derive(Clone, Component, Reflect)]
pub struct TurtleShape; pub struct TurtleShape;
#[derive(Clone, Component, Inspectable, Default)] #[derive(Clone, Component, Reflect, Default)]
pub struct Colors { pub struct Colors {
color: Color, color: Color,
fill_color: Color, fill_color: Color,
} }
#[derive(Component, Inspectable, Default)] #[derive(Component, Reflect, Default)]
pub enum TurtleCommand { pub enum TurtleCommand {
Forward(Length), Forward(Length),
Backward(Length), Backward(Length),
@ -138,6 +146,8 @@ pub enum TurtleCommand {
Right(Angle<f32>), Right(Angle<f32>),
PenUp, PenUp,
PenDown, PenDown,
BeginFill,
EndFill,
#[default] #[default]
Pause, Pause,
Circle { Circle {
@ -147,22 +157,33 @@ pub enum TurtleCommand {
} }
fn setup(mut commands: Commands) { fn setup(mut commands: Commands) {
let animator = Animator::new(Tween::new( let animator = Animator::new(
EaseFunction::QuadraticInOut, Tween::new(
TweeningType::PingPong, EaseFunction::QuadraticInOut,
Duration::from_millis(500), Duration::from_millis(500),
TransformScaleLens { TransformScaleLens {
start: Vec3::new(1., 1., 0.), start: Vec3::new(1., 1., 0.),
end: Vec3::new(1.3, 1.3, 0.), end: Vec3::new(1.3, 1.3, 0.),
}, },
)); )
commands.spawn_bundle(Camera2dBundle::default()); .with_repeat_strategy(bevy_tweening::RepeatStrategy::MirroredRepeat)
.with_repeat_count(RepeatCount::Infinite),
);
commands.spawn(Camera2dBundle::default());
let mut turtle_bundle = Turtle::default(); let mut turtle_bundle = Turtle::default();
turtle_bundle.set_commands(circle_star()); let mut tcommands = vec![];
commands //for _ in 0..100 {
.spawn_bundle(turtle_bundle) tcommands.append(&mut paths::geometry_task::geometry_task());
.insert(animator) //tcommands.append(&mut paths::circle_star::circle_star());
.insert(TurtleShape); //}
turtle_bundle.set_commands(tcommands);
commands.spawn((
turtle_bundle,
animator,
TurtleShape,
Fill::color(Color::MIDNIGHT_BLUE),
Stroke::color(Color::BLACK),
));
} }
fn draw_lines( fn draw_lines(
@ -171,7 +192,7 @@ fn draw_lines(
mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>, mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>,
mut query_event: EventReader<TweenCompleted>, // TODO: howto attach only to the right event? mut query_event: EventReader<TweenCompleted>, // TODO: howto attach only to the right event?
) { ) {
for ev in query_event.iter() { for _ev in query_event.iter() {
let mut tcmd = tcmd.single_mut(); let mut tcmd = tcmd.single_mut();
run_animation_step(&mut commands, &mut tcmd, &mut turtle) run_animation_step(&mut commands, &mut tcmd, &mut turtle)
} }
@ -188,7 +209,7 @@ fn keypresses(
tcmd.state = TurtleState { tcmd.state = TurtleState {
start: Vec2::ZERO, start: Vec2::ZERO,
heading: Angle::degrees(0.), heading: Angle::degrees(0.),
speed: 500, speed: 1,
index: 0, index: 0,
drawing: true, drawing: true,
}; };

15
src/turtle/builders.rs Normal file
View File

@ -0,0 +1,15 @@
use bevy_prototype_lyon::prelude::PathBuilder;
use super::state::TurtleState;
/// A turtle that combines its commands to one closed shape with the `close()` command.
pub struct ShapeBuilder {
state: TurtleState,
builder: PathBuilder,
}
/// A turtle that draws a filled shape. End the filling process with the `end()` command.
pub struct FilledBuilder {
state: TurtleState,
builder: PathBuilder,
}

28
src/turtle/state.rs Normal file
View File

@ -0,0 +1,28 @@
use bevy::prelude::{Color, Transform};
use crate::{
datatypes::{angle::Angle, Coordinate, Speed, Visibility},
structs::{DrawState, DrawingSegment},
turtle::Precision,
};
/// Describing the full state of a turtle.
pub struct TurtleState {
drawing: Vec<DrawingSegment>,
position: Coordinate,
heading: Angle<Precision>,
color: Color,
draw_state: DrawState,
visible: Visibility,
shape_transform: Transform,
speed: Speed,
}
impl TurtleState {
pub fn add_segment(&mut self, seg: DrawingSegment) {
self.drawing.push(seg);
}
pub fn drawing(&self) -> bool {
self.draw_state == DrawState::PenDown
}
}

72
src/turtle/turtle.rs Normal file
View File

@ -0,0 +1,72 @@
use bevy::prelude::{Color, Component};
use crate::{
structs::{DrawElement, DrawingSegment, MoveCommand},
turtle_state::TurtleDraw,
};
use super::state::TurtleState;
/// A default turtle drawing lines each line is one Segment.
#[derive(Component)]
pub struct Turtle {
state: TurtleState,
}
pub struct FilledTurtle {
state: TurtleState,
drawing: Vec<DrawingSegment>,
color: Color,
}
impl Turtle {
pub fn begin_fill(self, color: Color) -> FilledTurtle {
FilledTurtle {
state: self.state,
drawing: vec![],
color,
}
}
}
impl TurtleDraw for Turtle {
fn forward(&mut self, length: crate::datatypes::length::Length) -> &mut Self {
let move_command = MoveCommand::Forward(length);
self.state
.add_segment(DrawingSegment::Single(if self.state.drawing() {
DrawElement::Draw(move_command)
} else {
DrawElement::Move(move_command)
}));
self
}
fn backward(&mut self, length: crate::datatypes::length::Length) -> &mut Self {
todo!()
}
fn circle(
&mut self,
radius: crate::datatypes::length::Length,
angle: crate::datatypes::angle::Angle<super::Precision>,
) -> &mut Self {
todo!()
}
fn goto(&mut self, coordinate: crate::datatypes::Coordinate) -> &mut Self {
todo!()
}
fn home(&mut self) -> &mut Self {
todo!()
}
fn dot(&mut self, size: crate::datatypes::length::Length) -> &mut Self {
todo!()
}
fn stamp(&mut self, size: crate::datatypes::length::Length) -> &mut Self {
todo!()
}
}

View File

@ -1,10 +1,10 @@
use std::time::Duration; use std::time::Duration;
use bevy::prelude::{Quat, Transform, Vec2, Vec3}; use bevy::prelude::{default, Color, Quat, Transform, Vec2, Vec3};
use bevy_prototype_lyon::prelude::Path; use bevy_prototype_lyon::prelude::{Fill, Path, Stroke};
use bevy_tweening::{ use bevy_tweening::{
lens::{TransformPositionLens, TransformRotateZLens}, lens::{TransformPositionLens, TransformRotateZLens},
Animator, EaseFunction, Tween, TweeningType, Animator, EaseFunction, Tween,
}; };
use crate::{ use crate::{
@ -20,6 +20,8 @@ pub struct TurtleStep {
pub turtle_animation: Option<Tween<Transform>>, pub turtle_animation: Option<Tween<Transform>>,
pub line_segment: Option<TurtleGraphElement>, pub line_segment: Option<TurtleGraphElement>,
pub line_animation: Option<Animator<Path>>, pub line_animation: Option<Animator<Path>>,
pub fill: Option<Fill>,
pub stroke: Option<Stroke>,
} }
pub fn turtle_turn(state: &mut TurtleState, angle_to_turn: Angle<Precision>) -> TurtleStep { pub fn turtle_turn(state: &mut TurtleState, angle_to_turn: Angle<Precision>) -> TurtleStep {
@ -27,7 +29,6 @@ pub fn turtle_turn(state: &mut TurtleState, angle_to_turn: Angle<Precision>) ->
let end = state.heading + angle_to_turn; let end = state.heading + angle_to_turn;
let animation = Tween::new( let animation = Tween::new(
EaseFunction::QuadraticInOut, EaseFunction::QuadraticInOut,
TweeningType::Once,
Duration::from_millis(state.speed), Duration::from_millis(state.speed),
TransformRotateZLens { TransformRotateZLens {
start: start.to_radians().value(), start: start.to_radians().value(),
@ -43,6 +44,8 @@ pub fn turtle_turn(state: &mut TurtleState, angle_to_turn: Angle<Precision>) ->
turtle_animation: Some(animation), turtle_animation: Some(animation),
line_segment: Some(line), line_segment: Some(line),
line_animation: None, line_animation: None,
fill: None,
stroke: None,
} }
} }
@ -51,7 +54,6 @@ pub fn turtle_move(state: &mut TurtleState, length: Precision) -> TurtleStep {
let end = state.start + (Vec2::from_angle(state.heading.to_radians().value()) * length); let end = state.start + (Vec2::from_angle(state.heading.to_radians().value()) * length);
let turtle_movement_animation = Tween::new( let turtle_movement_animation = Tween::new(
EaseFunction::QuadraticInOut, EaseFunction::QuadraticInOut,
TweeningType::Once,
Duration::from_millis(state.speed), Duration::from_millis(state.speed),
TransformPositionLens { TransformPositionLens {
start: start.extend(0.), start: start.extend(0.),
@ -66,7 +68,6 @@ pub fn turtle_move(state: &mut TurtleState, length: Precision) -> TurtleStep {
}; };
let line_animator = Animator::new(Tween::new( let line_animator = Animator::new(Tween::new(
EaseFunction::QuadraticInOut, EaseFunction::QuadraticInOut,
TweeningType::Once,
Duration::from_millis(state.speed), Duration::from_millis(state.speed),
LineAnimationLens::new(start, end), LineAnimationLens::new(start, end),
)); ));
@ -75,6 +76,8 @@ pub fn turtle_move(state: &mut TurtleState, length: Precision) -> TurtleStep {
turtle_animation: Some(turtle_movement_animation), turtle_animation: Some(turtle_movement_animation),
line_segment: Some(line), line_segment: Some(line),
line_animation: Some(line_animator), line_animation: Some(line_animator),
fill: Some(Fill::color(Color::MIDNIGHT_BLUE)),
stroke: Some(Stroke::color(Color::BLACK)),
} }
} }
@ -92,7 +95,6 @@ pub fn turtle_circle(
let turtle_movement_animation = Tween::new( let turtle_movement_animation = Tween::new(
EaseFunction::QuadraticInOut, EaseFunction::QuadraticInOut,
TweeningType::Once,
Duration::from_millis(state.speed), Duration::from_millis(state.speed),
CircleMovementLens { CircleMovementLens {
start: Transform { start: Transform {
@ -123,7 +125,6 @@ pub fn turtle_circle(
}; };
let line_animator = Animator::new(Tween::new( let line_animator = Animator::new(Tween::new(
EaseFunction::QuadraticInOut, EaseFunction::QuadraticInOut,
TweeningType::Once,
Duration::from_millis(state.speed), Duration::from_millis(state.speed),
CircleAnimationLens { CircleAnimationLens {
start_pos: state.start, start_pos: state.start,
@ -139,5 +140,7 @@ pub fn turtle_circle(
turtle_animation: Some(turtle_movement_animation), turtle_animation: Some(turtle_movement_animation),
line_segment: Some(line), line_segment: Some(line),
line_animation: Some(line_animator), line_animation: Some(line_animator),
fill: Some(Fill::color(Color::MIDNIGHT_BLUE)),
stroke: Some(Stroke::color(Color::BLACK)),
} }
} }

61
src/turtle_state.rs Normal file
View File

@ -0,0 +1,61 @@
use bevy::prelude::{Color, Transform};
use bevy_prototype_lyon::prelude::PathBuilder;
use crate::{
datatypes::{angle::Angle, length::Length, Coordinate, Speed, Visibility},
structs::DrawingSegment,
turtle::Precision,
};
/// Describing the full state of a turtle.
pub struct TurtleState {
drawing: Vec<DrawingSegment>,
position: Coordinate,
heading: Angle<Precision>,
color: Color,
fill_color: Color,
visible: Visibility,
shape_transform: Transform,
speed: Speed,
}
/// A default turtle drawing lines each line is one Segment.
pub struct Turtle {
state: TurtleState,
}
/// A turtle that combines its commands to one closed shape with the `close()` command.
pub struct ShapeBuilder {
state: TurtleState,
builder: PathBuilder,
}
/// A turtle that draws a filled shape. End the filling process with the `end()` command.
pub struct FilledBuilder {
state: TurtleState,
builder: PathBuilder,
}
pub trait TurtleDraw {
fn forward(&mut self, length: Length) -> &mut Self;
fn backward(&mut self, length: Length) -> &mut Self;
fn circle(&mut self, radius: Length, angle: Angle<Precision>) -> &mut Self;
fn goto(&mut self, coordinate: Coordinate) -> &mut Self;
fn home(&mut self) -> &mut Self;
fn dot(&mut self, size: Length) -> &mut Self;
fn stamp(&mut self, size: Length) -> &mut Self;
}
pub trait TurtleTurn {
fn left(&mut self, angle: Angle<Precision>) -> &mut Self;
fn right(&mut self, angle: Angle<Precision>) -> &mut Self;
fn look_at(&mut self, coordinate: Coordinate) -> &mut Self;
}
pub trait TurtleHistory {
fn reset(&mut self) -> &mut Self;
fn clear(&mut self) -> &mut Self;
fn undo(&mut self) -> &mut Self;
fn clear_stamps(&mut self) -> &mut Self;
}