Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fb0022280
|
||
|
|
9a551573cb
|
||
|
|
03af02b4e6
|
||
|
|
5f4e47f540
|
||
|
|
f9a4c1d81a
|
||
|
|
ee2c869a03
|
||
|
|
d8a5c67fd5
|
||
|
|
d16607ff4d
|
||
|
|
a5a1be2392
|
||
|
|
7a543685d3 | ||
|
|
62ca3eb79d
|
||
|
|
4d49dd44ba
|
||
|
|
1d1787b8c0 | ||
|
|
92458b55e5 |
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
/.vscode
|
||||||
Generated
+1668
-1045
File diff suppressed because it is too large
Load Diff
+7
-4
@@ -7,10 +7,13 @@ 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.1"
|
bevy-inspector-egui = "0.19"
|
||||||
bevy_prototype_lyon = "0.6"
|
bevy_egui = "0.21"
|
||||||
bevy_tweening = "0.5.0"
|
egui = "0.22"
|
||||||
|
bevy_prototype_lyon = {version="0.9"}
|
||||||
|
bevy_tweening = {version="0.8"}
|
||||||
|
num-traits = "0.2"
|
||||||
|
|
||||||
# Enable a small amount of optimization in debug mode
|
# Enable a small amount of optimization in debug mode
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
use bevy::prelude::{Color, Commands, Query, Transform, With};
|
||||||
|
use bevy_prototype_lyon::prelude::{Fill, Stroke};
|
||||||
|
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<Transform>, With<TurtleShape>>,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
match tcmd.get_next() {
|
||||||
|
Some(TurtleStep {
|
||||||
|
turtle_animation: Some(turtle_animation),
|
||||||
|
line_segment: Some(graph_element_to_draw),
|
||||||
|
line_animation: Some(line_animation),
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
}) => {
|
||||||
|
let mut turtle = turtle.single_mut();
|
||||||
|
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 {
|
||||||
|
TurtleGraphElement::TurtleLine(line) => {
|
||||||
|
commands.spawn((line, line_animation, fill, stroke));
|
||||||
|
}
|
||||||
|
TurtleGraphElement::Noop => (),
|
||||||
|
TurtleGraphElement::TurtleCircle(circle) => {
|
||||||
|
commands.spawn((circle, line_animation, fill, stroke));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_prototype_lyon::prelude::*;
|
||||||
|
use bevy_tweening::{
|
||||||
|
component_animator_system, Animator, EaseFunction, Lens, Sequence, Tween, TweeningPlugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.insert_resource(Msaa::Sample4)
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugins(ShapePlugin)
|
||||||
|
.add_plugins(TweeningPlugin)
|
||||||
|
.add_systems(Startup, setup_system)
|
||||||
|
.add_systems(Update, component_animator_system::<Path>)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_system(mut commands: Commands) {
|
||||||
|
let mut path_builder = PathBuilder::new();
|
||||||
|
path_builder.line_to(Vec2::new(100., 0.));
|
||||||
|
let ops = vec![
|
||||||
|
GElem::Circle {
|
||||||
|
center: Vec2::ZERO,
|
||||||
|
radii: Vec2::splat(100.),
|
||||||
|
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();
|
||||||
|
|
||||||
|
commands.spawn(Camera2dBundle::default());
|
||||||
|
let mut seq = Sequence::with_capacity(ops.len());
|
||||||
|
for (step, op) in ops.clone().into_iter().enumerate() {
|
||||||
|
let mut done = ops.clone();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
use bevy::prelude::Vec2;
|
||||||
|
|
||||||
|
pub mod angle;
|
||||||
|
pub mod length;
|
||||||
|
|
||||||
|
pub type Coordinate = Vec2;
|
||||||
|
pub type Visibility = bool;
|
||||||
|
pub type Speed = u32;
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
|
||||||
|
use std::{
|
||||||
|
f32::consts::PI,
|
||||||
|
ops::{Add, Div, Mul, Neg, Rem, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy::reflect::Reflect;
|
||||||
|
|
||||||
|
use crate::turtle::Precision;
|
||||||
|
|
||||||
|
#[derive(Reflect, Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum AngleUnit<T: Default> {
|
||||||
|
Degrees(T),
|
||||||
|
Radians(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default> Default for AngleUnit<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Degrees(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Copy, Default, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Angle<T: Default> {
|
||||||
|
value: AngleUnit<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Clone + Rem<T, Output = T>> Rem<T> for Angle<T> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn rem(self, rhs: T) -> Self::Output {
|
||||||
|
match self.value {
|
||||||
|
AngleUnit::Degrees(v) => Self::Output::degrees(v % rhs),
|
||||||
|
AngleUnit::Radians(v) => Self::Output::radians(v % rhs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Clone + Mul<T, Output = T>> Mul<T> for Angle<T> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, rhs: T) -> Self::Output {
|
||||||
|
match self.value {
|
||||||
|
AngleUnit::Degrees(v) => Self::Output::degrees(v * rhs),
|
||||||
|
AngleUnit::Radians(v) => Self::Output::radians(v * rhs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Angle<Precision> {
|
||||||
|
pub fn limit_smaller_than_full_circle(self) -> Self {
|
||||||
|
match self.value {
|
||||||
|
AngleUnit::Degrees(v) => Self {
|
||||||
|
value: AngleUnit::Degrees(v % 360.),
|
||||||
|
},
|
||||||
|
AngleUnit::Radians(v) => Self {
|
||||||
|
value: AngleUnit::Radians(v % (2. * PI)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Default + Clone + Div<T, Output = T>> Div<T> for Angle<T> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn div(self, rhs: T) -> Self::Output {
|
||||||
|
match self.value {
|
||||||
|
AngleUnit::Degrees(v) => Self::Output::degrees(v / rhs),
|
||||||
|
AngleUnit::Radians(v) => Self::Output::radians(v / rhs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Clone + std::ops::Neg<Output = T>> Neg for Angle<T> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
match self.value {
|
||||||
|
AngleUnit::Degrees(v) => Self::Output::degrees(-v),
|
||||||
|
AngleUnit::Radians(v) => Self::Output::radians(-v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Clone + std::ops::Neg<Output = T>> Neg for &Angle<T> {
|
||||||
|
type Output = Angle<T>;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
match self.value.clone() {
|
||||||
|
AngleUnit::Degrees(v) => Self::Output::degrees(-v),
|
||||||
|
AngleUnit::Radians(v) => Self::Output::radians(-v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Clone> Angle<T> {
|
||||||
|
pub fn degrees(value: T) -> Angle<T> {
|
||||||
|
Self {
|
||||||
|
value: AngleUnit::Degrees(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn radians(value: T) -> Angle<T> {
|
||||||
|
Self {
|
||||||
|
value: AngleUnit::Radians(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn value(&self) -> T {
|
||||||
|
match self.value.clone() {
|
||||||
|
AngleUnit::Degrees(v) => v,
|
||||||
|
AngleUnit::Radians(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + num_traits::float::Float> Angle<T> {
|
||||||
|
pub fn to_radians(self) -> Self {
|
||||||
|
match self.value {
|
||||||
|
AngleUnit::Degrees(v) => Self {
|
||||||
|
value: AngleUnit::Radians(v.to_radians()),
|
||||||
|
},
|
||||||
|
AngleUnit::Radians(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn to_degrees(self) -> Self {
|
||||||
|
match self.value {
|
||||||
|
AngleUnit::Degrees(_) => self,
|
||||||
|
AngleUnit::Radians(v) => Self {
|
||||||
|
value: AngleUnit::Degrees(v.to_degrees()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Add<Output = T> + Default + num_traits::float::Float> Add for Angle<T> {
|
||||||
|
type Output = Angle<T>;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
match (self.value, rhs.value) {
|
||||||
|
(AngleUnit::Degrees(v), AngleUnit::Degrees(o)) => Self::Output {
|
||||||
|
value: AngleUnit::Degrees(v + o),
|
||||||
|
},
|
||||||
|
(AngleUnit::Degrees(v), AngleUnit::Radians(o)) => Self::Output {
|
||||||
|
value: AngleUnit::Radians(v.to_radians() + o),
|
||||||
|
},
|
||||||
|
(AngleUnit::Radians(v), AngleUnit::Degrees(o)) => Self::Output {
|
||||||
|
value: AngleUnit::Radians(v + o.to_radians()),
|
||||||
|
},
|
||||||
|
(AngleUnit::Radians(v), AngleUnit::Radians(o)) => Self::Output {
|
||||||
|
value: AngleUnit::Radians(v + o),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sub<Output = T> + Default + num_traits::float::Float> Sub for Angle<T> {
|
||||||
|
type Output = Angle<T>;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
match (self.value, rhs.value) {
|
||||||
|
(AngleUnit::Degrees(v), AngleUnit::Degrees(o)) => Self::Output {
|
||||||
|
value: AngleUnit::Degrees(v - o),
|
||||||
|
},
|
||||||
|
(AngleUnit::Degrees(v), AngleUnit::Radians(o)) => Self::Output {
|
||||||
|
value: AngleUnit::Radians(v.to_radians() - o),
|
||||||
|
},
|
||||||
|
(AngleUnit::Radians(v), AngleUnit::Degrees(o)) => Self::Output {
|
||||||
|
value: AngleUnit::Radians(v - o.to_radians()),
|
||||||
|
},
|
||||||
|
(AngleUnit::Radians(v), AngleUnit::Radians(o)) => Self::Output {
|
||||||
|
value: AngleUnit::Radians(v - o),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_to_radians() {
|
||||||
|
let radi = Angle::radians(30f32.to_radians());
|
||||||
|
let degr = Angle::degrees(30f32);
|
||||||
|
let converted = degr.to_radians();
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
use bevy::reflect::Reflect;
|
||||||
|
|
||||||
|
use crate::turtle::Precision;
|
||||||
|
|
||||||
|
#[derive(Reflect, Default, Copy, Clone, Debug)]
|
||||||
|
pub struct Length(pub Precision);
|
||||||
+1
-1
@@ -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;
|
||||||
|
|
||||||
|
|||||||
+15
-8
@@ -1,6 +1,12 @@
|
|||||||
|
mod animation_step;
|
||||||
|
mod datatypes;
|
||||||
mod debug;
|
mod debug;
|
||||||
|
mod paths;
|
||||||
|
mod primitives;
|
||||||
|
mod structs;
|
||||||
mod turtle;
|
mod turtle;
|
||||||
mod turtle_shapes;
|
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::*;
|
||||||
@@ -8,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: 400.0,
|
primary_window: Some(Window {
|
||||||
height: 400.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()
|
||||||
})
|
}),
|
||||||
.add_plugins(DefaultPlugins)
|
..default()
|
||||||
|
}))
|
||||||
.add_plugin(ShapePlugin)
|
.add_plugin(ShapePlugin)
|
||||||
.add_plugin(debug::DebugPlugin)
|
.add_plugin(debug::DebugPlugin)
|
||||||
.add_plugin(TurtlePlugin)
|
.add_plugin(TurtlePlugin)
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use crate::{
|
||||||
|
datatypes::{angle::Angle, length::Length},
|
||||||
|
turtle::TurtleCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn circle_star() -> Vec<TurtleCommand> {
|
||||||
|
vec![
|
||||||
|
TurtleCommand::Right(Angle::degrees(36.)),
|
||||||
|
TurtleCommand::Forward(Length(200.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Circle {
|
||||||
|
radius: Length(20.),
|
||||||
|
angle: Angle::degrees(324.),
|
||||||
|
},
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Forward(Length(200.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Circle {
|
||||||
|
radius: Length(20.),
|
||||||
|
angle: Angle::degrees(324.),
|
||||||
|
},
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Forward(Length(200.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Circle {
|
||||||
|
radius: Length(20.),
|
||||||
|
angle: Angle::degrees(324.),
|
||||||
|
},
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Forward(Length(200.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Circle {
|
||||||
|
radius: Length(20.),
|
||||||
|
angle: Angle::degrees(324.),
|
||||||
|
},
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Forward(Length(200.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Circle {
|
||||||
|
radius: Length(20.),
|
||||||
|
angle: Angle::degrees(324.),
|
||||||
|
},
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use crate::{
|
||||||
|
datatypes::{angle::Angle, length::Length},
|
||||||
|
turtle::TurtleCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn geometry_task() -> Vec<TurtleCommand> {
|
||||||
|
let mut before = vec![
|
||||||
|
TurtleCommand::Forward(Length(100.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Backward(Length(100.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Forward(Length(100.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(45.)),
|
||||||
|
];
|
||||||
|
for _ in 0..10 {
|
||||||
|
let mut dash = vec![
|
||||||
|
TurtleCommand::PenUp,
|
||||||
|
TurtleCommand::Forward(Length(5.)),
|
||||||
|
TurtleCommand::PenDown,
|
||||||
|
TurtleCommand::Forward(Length(5.)),
|
||||||
|
];
|
||||||
|
before.append(&mut dash);
|
||||||
|
}
|
||||||
|
let mut after = vec![
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Forward(Length(50.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Forward(Length(100.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(90.)),
|
||||||
|
TurtleCommand::Forward(Length(50.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(45.)),
|
||||||
|
TurtleCommand::Forward(Length(100.)),
|
||||||
|
TurtleCommand::Left(Angle::degrees(120.)),
|
||||||
|
TurtleCommand::Forward(Length(100.)),
|
||||||
|
TurtleCommand::Left(Angle::degrees(120.)),
|
||||||
|
TurtleCommand::Forward(Length(100.)),
|
||||||
|
TurtleCommand::Right(Angle::degrees(150.)),
|
||||||
|
TurtleCommand::Forward(Length(100.)),
|
||||||
|
];
|
||||||
|
before.append(&mut after);
|
||||||
|
before
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod circle_star;
|
||||||
|
pub mod geometry_task;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod animation;
|
||||||
|
pub mod bundles;
|
||||||
|
pub mod components;
|
||||||
|
pub mod turtle_primitives;
|
||||||
|
pub mod turtle_shapes;
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
use bevy::prelude::{Quat, Transform, Vec2};
|
||||||
|
use bevy_prototype_lyon::{
|
||||||
|
prelude::{Path, PathBuilder, ShapePath},
|
||||||
|
shapes,
|
||||||
|
};
|
||||||
|
use bevy_tweening::Lens;
|
||||||
|
|
||||||
|
use crate::{datatypes::angle::Angle, turtle::Precision};
|
||||||
|
|
||||||
|
pub(crate) struct LineAnimationLens {
|
||||||
|
start: Vec2,
|
||||||
|
end: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineAnimationLens {
|
||||||
|
pub(crate) fn new(start: Vec2, end: Vec2) -> Self {
|
||||||
|
Self { start, end }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lens<Path> for LineAnimationLens {
|
||||||
|
fn lerp(&mut self, target: &mut Path, ratio: f32) {
|
||||||
|
let line = shapes::Line(self.start, self.start + ((self.end - self.start) * ratio));
|
||||||
|
*target = ShapePath::build_as(&line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct CircleAnimationLens {
|
||||||
|
pub start_pos: Vec2,
|
||||||
|
pub center: Vec2,
|
||||||
|
pub radii: Vec2,
|
||||||
|
pub start: Angle<Precision>,
|
||||||
|
pub end: Angle<Precision>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lens<Path> for CircleAnimationLens {
|
||||||
|
fn lerp(&mut self, target: &mut Path, ratio: f32) {
|
||||||
|
let mut path_builder = PathBuilder::new();
|
||||||
|
path_builder.move_to(self.start_pos);
|
||||||
|
// The center point of the radius, then the radii in x and y direction, then the angle that will be drawn, then the x_rotation ?
|
||||||
|
path_builder.arc(
|
||||||
|
self.center,
|
||||||
|
self.radii,
|
||||||
|
(self.start + ((self.end - self.start) * ratio))
|
||||||
|
.to_radians()
|
||||||
|
.value(),
|
||||||
|
0.,
|
||||||
|
);
|
||||||
|
let line = path_builder.build();
|
||||||
|
*target = ShapePath::build_as(&line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct CircleMovementLens {
|
||||||
|
pub center: Vec2,
|
||||||
|
pub start: Transform,
|
||||||
|
pub end: Angle<Precision>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lens<Transform> for CircleMovementLens {
|
||||||
|
fn lerp(&mut self, target: &mut Transform, ratio: f32) {
|
||||||
|
let angle = self.end * ratio;
|
||||||
|
let mut rotated = self.start;
|
||||||
|
|
||||||
|
rotated.rotate_around(
|
||||||
|
self.center.extend(0.),
|
||||||
|
Quat::from_rotation_z(angle.to_radians().value()),
|
||||||
|
);
|
||||||
|
|
||||||
|
*target = rotated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
use bevy::{
|
||||||
|
prelude::{default, Bundle, Component, Name, Vec2},
|
||||||
|
reflect::Reflect,
|
||||||
|
};
|
||||||
|
use bevy_prototype_lyon::{
|
||||||
|
entity::ShapeBundle,
|
||||||
|
prelude::{Fill, GeometryBuilder, PathBuilder, Stroke},
|
||||||
|
shapes::Line,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
datatypes::angle::Angle,
|
||||||
|
turtle::{Colors, Precision, TurtleCommand, TurtleCommands},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::turtle_shapes;
|
||||||
|
|
||||||
|
#[derive(Bundle, Reflect, Default)]
|
||||||
|
pub struct TurtleDrawLine {
|
||||||
|
#[reflect(ignore)]
|
||||||
|
line: ShapeBundle,
|
||||||
|
name: Name,
|
||||||
|
marker: LineMarker,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Default, Reflect)]
|
||||||
|
struct LineMarker;
|
||||||
|
|
||||||
|
impl TurtleDrawLine {
|
||||||
|
pub(crate) fn new(start: Vec2, _end: Vec2, index: u64) -> Self {
|
||||||
|
let bundle = ShapeBundle {
|
||||||
|
path: GeometryBuilder::build_as(&Line(start, start)),
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
line: bundle,
|
||||||
|
name: Name::new(format!("Line {}", index)),
|
||||||
|
marker: LineMarker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Bundle, Reflect, Default)]
|
||||||
|
|
||||||
|
pub struct TurtleDrawCircle {
|
||||||
|
#[reflect(ignore)]
|
||||||
|
line: ShapeBundle,
|
||||||
|
name: Name,
|
||||||
|
marker: CircleMarker,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Default, Reflect)]
|
||||||
|
struct CircleMarker;
|
||||||
|
|
||||||
|
impl TurtleDrawCircle {
|
||||||
|
pub(crate) fn new(
|
||||||
|
center: Vec2,
|
||||||
|
radii: Vec2,
|
||||||
|
angle: Angle<Precision>,
|
||||||
|
index: u64,
|
||||||
|
start: Vec2,
|
||||||
|
end: Vec2,
|
||||||
|
) -> Self {
|
||||||
|
let mut path_builder = PathBuilder::new();
|
||||||
|
path_builder.move_to(start);
|
||||||
|
// The center point of the radius - this is responsible for the orientation of the ellipse,
|
||||||
|
// then the radii in x and y direction - this can be rotated using the x_rotation parameter,
|
||||||
|
// then the angle - the part of the circle that will be drawn like (PI/2.0) for a quarter circle,
|
||||||
|
// then the x_rotation (maybe the rotation of the radii?)
|
||||||
|
path_builder.arc(center, radii, angle.to_radians().value(), 0.);
|
||||||
|
let line = path_builder.build();
|
||||||
|
println!("Draw Circle: {} {} {:?}", center, radii, angle);
|
||||||
|
|
||||||
|
let bundle = ShapeBundle {
|
||||||
|
path: GeometryBuilder::build_as(&line),
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
line: bundle,
|
||||||
|
name: Name::new(format!("Circle {}", index)),
|
||||||
|
marker: CircleMarker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Bundle)]
|
||||||
|
pub struct Turtle {
|
||||||
|
colors: Colors,
|
||||||
|
commands: TurtleCommands,
|
||||||
|
name: Name,
|
||||||
|
shape: ShapeBundle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Turtle {
|
||||||
|
fn default() -> Self {
|
||||||
|
let bundle = ShapeBundle {
|
||||||
|
path: GeometryBuilder::build_as(&turtle_shapes::turtle()),
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
colors: Colors::default(),
|
||||||
|
commands: TurtleCommands::new(vec![]),
|
||||||
|
name: Name::new("Turtle"),
|
||||||
|
shape: bundle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Turtle {
|
||||||
|
/* pub fn set_color(&mut self, color: Color) {
|
||||||
|
self.colors.color = color;
|
||||||
|
}
|
||||||
|
pub fn set_fill_color(&mut self, color: Color) {
|
||||||
|
self.colors.fill_color = color;
|
||||||
|
}
|
||||||
|
pub fn get_colors(&self) -> &Colors {
|
||||||
|
&self.colors
|
||||||
|
}
|
||||||
|
pub fn forward(&mut self) -> &mut Self {
|
||||||
|
self.commands
|
||||||
|
.commands
|
||||||
|
.push(TurtleCommand::Forward(Length(100.0)));
|
||||||
|
self
|
||||||
|
} */
|
||||||
|
pub fn set_commands(&mut self, commands: Vec<TurtleCommand>) {
|
||||||
|
self.commands = TurtleCommands::new(commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use bevy::prelude::Vec2;
|
||||||
|
|
||||||
|
use crate::{datatypes::angle::Angle, turtle::Precision};
|
||||||
|
|
||||||
|
pub struct TurtleAction {
|
||||||
|
heading: Angle<Precision>,
|
||||||
|
position: Vec2,
|
||||||
|
primitive: TurtleActionType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TurtleActionType {
|
||||||
|
Straight {
|
||||||
|
target: Vec2,
|
||||||
|
},
|
||||||
|
Rotate {
|
||||||
|
angle: Angle<Precision>,
|
||||||
|
},
|
||||||
|
Circle {
|
||||||
|
center: Vec2,
|
||||||
|
radii: Vec2,
|
||||||
|
extent: Angle<Precision>,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -3,31 +3,33 @@ use std::f32::consts::PI;
|
|||||||
use bevy::prelude::Vec2;
|
use bevy::prelude::Vec2;
|
||||||
use bevy_prototype_lyon::prelude::{Path, PathBuilder};
|
use bevy_prototype_lyon::prelude::{Path, PathBuilder};
|
||||||
|
|
||||||
|
use crate::turtle::Precision;
|
||||||
|
|
||||||
pub fn turtle() -> Path {
|
pub fn turtle() -> Path {
|
||||||
let polygon = &[
|
let polygon: &[[Precision; 2]; 23] = &[
|
||||||
[-2.5f32, 14.0f32],
|
[-2.5, 14.0],
|
||||||
[-1.25f32, 10.0f32],
|
[-1.25, 10.0],
|
||||||
[-4.0f32, 7.0f32],
|
[-4.0, 7.0],
|
||||||
[-7.0f32, 9.0f32],
|
[-7.0, 9.0],
|
||||||
[-9.0f32, 8.0f32],
|
[-9.0, 8.0],
|
||||||
[-6.0f32, 5.0f32],
|
[-6.0, 5.0],
|
||||||
[-7.0f32, 1.0f32],
|
[-7.0, 1.0],
|
||||||
[-5.0f32, -3.0f32],
|
[-5.0, -3.0],
|
||||||
[-8.0f32, -6.0f32],
|
[-8.0, -6.0],
|
||||||
[-6.0f32, -8.0f32],
|
[-6.0, -8.0],
|
||||||
[-4.0f32, -5.0f32],
|
[-4.0, -5.0],
|
||||||
[0.0f32, -7.0f32],
|
[0.0, -7.0],
|
||||||
[4.0f32, -5.0f32],
|
[4.0, -5.0],
|
||||||
[6.0f32, -8.0f32],
|
[6.0, -8.0],
|
||||||
[8.0f32, -6.0f32],
|
[8.0, -6.0],
|
||||||
[5.0f32, -3.0f32],
|
[5.0, -3.0],
|
||||||
[7.0f32, 1.0f32],
|
[7.0, 1.0],
|
||||||
[6.0f32, 5.0f32],
|
[6.0, 5.0],
|
||||||
[9.0f32, 8.0f32],
|
[9.0, 8.0],
|
||||||
[7.0f32, 9.0f32],
|
[7.0, 9.0],
|
||||||
[4.0f32, 7.0f32],
|
[4.0, 7.0],
|
||||||
[1.25f32, 10.0f32],
|
[1.25, 10.0],
|
||||||
[2.5f32, 14.0f32],
|
[2.5, 14.0],
|
||||||
];
|
];
|
||||||
let mut turtle_path = PathBuilder::new();
|
let mut turtle_path = PathBuilder::new();
|
||||||
turtle_path.line_to(Vec2::new(1.0, 1.0));
|
turtle_path.line_to(Vec2::new(1.0, 1.0));
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
+167
-182
@@ -1,14 +1,23 @@
|
|||||||
use std::{f32::consts::PI, time::Duration};
|
pub mod builders;
|
||||||
|
pub mod state;
|
||||||
|
pub mod turtle;
|
||||||
|
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::{
|
||||||
lens::{TransformPositionLens, TransformRotateZLens},
|
component_animator_system, lens::TransformScaleLens, Animator, EaseFunction, RepeatCount,
|
||||||
Animator, EaseFunction, Sequence, Tween, TweeningPlugin, TweeningType,
|
Tween, TweenCompleted, TweeningPlugin,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::turtle_shapes;
|
#[allow(unused_imports)]
|
||||||
|
use crate::paths;
|
||||||
|
use crate::{
|
||||||
|
animation_step::run_animation_step,
|
||||||
|
datatypes::{angle::Angle, length::Length},
|
||||||
|
primitives::bundles::{Turtle, TurtleDrawCircle, TurtleDrawLine},
|
||||||
|
turtle_movement::TurtleStep,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct TurtlePlugin;
|
pub struct TurtlePlugin;
|
||||||
|
|
||||||
@@ -17,218 +26,194 @@ impl Plugin for TurtlePlugin {
|
|||||||
app.add_plugin(TweeningPlugin)
|
app.add_plugin(TweeningPlugin)
|
||||||
.add_startup_system(setup)
|
.add_startup_system(setup)
|
||||||
.add_system(keypresses)
|
.add_system(keypresses)
|
||||||
.register_inspectable::<Colors>()
|
.add_system(draw_lines)
|
||||||
.register_inspectable::<TurtleCommands>();
|
.add_system(component_animator_system::<Path>)
|
||||||
|
.register_type::<Colors>()
|
||||||
|
.register_type::<TurtleCommands>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Bundle)]
|
pub type Precision = f32;
|
||||||
pub struct Turtle {
|
|
||||||
colors: Colors,
|
|
||||||
commands: TurtleCommands,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Turtle {
|
#[derive(Component, Reflect)]
|
||||||
fn default() -> Self {
|
pub struct TurtleCommands {
|
||||||
Self {
|
commands: Vec<TurtleCommand>,
|
||||||
colors: Colors {
|
lines: Vec<TurtleGraphElement>,
|
||||||
color: Color::DARK_GRAY,
|
state: TurtleState,
|
||||||
fill_color: Color::BLACK,
|
|
||||||
},
|
|
||||||
commands: TurtleCommands(vec![
|
|
||||||
TurtleCommand::Forward(Length(100.)),
|
|
||||||
TurtleCommand::Left(Angle(90.)),
|
|
||||||
TurtleCommand::Forward(Length(100.)),
|
|
||||||
TurtleCommand::Left(Angle(90.)),
|
|
||||||
TurtleCommand::Forward(Length(100.)),
|
|
||||||
TurtleCommand::Left(Angle(90.)),
|
|
||||||
TurtleCommand::Forward(Length(100.)),
|
|
||||||
TurtleCommand::Left(Angle(90.)),
|
|
||||||
]), /*
|
|
||||||
shape: TurtleShape(GeometryBuilder::build_as(
|
|
||||||
&turtle_shapes::turtle(),
|
|
||||||
DrawMode::Outlined {
|
|
||||||
fill_mode: FillMode::color(Color::MIDNIGHT_BLUE),
|
|
||||||
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
|
|
||||||
},
|
|
||||||
Default::default(),
|
|
||||||
)), */
|
|
||||||
}
|
}
|
||||||
|
#[derive(Reflect)]
|
||||||
|
pub struct TurtleState {
|
||||||
|
pub start: Vec2,
|
||||||
|
pub heading: Angle<f32>,
|
||||||
|
pub speed: u64,
|
||||||
|
pub index: u64,
|
||||||
|
pub drawing: bool,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Turtle {
|
|
||||||
pub fn set_color(&mut self, color: Color) {
|
|
||||||
self.colors.color = color;
|
|
||||||
}
|
|
||||||
pub fn set_fill_color(&mut self, color: Color) {
|
|
||||||
self.colors.fill_color = color;
|
|
||||||
}
|
|
||||||
pub fn get_colors(&self) -> &Colors {
|
|
||||||
&self.colors
|
|
||||||
}
|
|
||||||
pub fn forward(&mut self) -> &mut Self {
|
|
||||||
self.commands.0.push(TurtleCommand::Forward(Length(100.0)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Inspectable)]
|
|
||||||
pub struct TurtleCommands(Vec<TurtleCommand>);
|
|
||||||
|
|
||||||
impl TurtleCommands {
|
impl TurtleCommands {
|
||||||
fn generate_tweenable(&self) -> Sequence<Transform> {
|
pub fn new(commands: Vec<TurtleCommand>) -> Self {
|
||||||
let mut seq = Sequence::with_capacity(self.0.len());
|
Self {
|
||||||
let mut pos = Vec2::ZERO;
|
commands,
|
||||||
let mut ori: f32 = 0.;
|
lines: vec![],
|
||||||
for op in &self.0 {
|
state: TurtleState {
|
||||||
match op {
|
start: Vec2::ZERO,
|
||||||
TurtleCommand::Forward(Length(x)) => {
|
heading: Angle::degrees(0.),
|
||||||
println!("Adding Forward");
|
speed: 2000,
|
||||||
let start = pos;
|
index: 0,
|
||||||
let end = pos + (Vec2::from_angle(ori) * *x as f32);
|
drawing: true,
|
||||||
seq = seq.then(Tween::new(
|
|
||||||
// accelerate and decelerate
|
|
||||||
EaseFunction::QuadraticInOut,
|
|
||||||
// Loop animation back and forth.
|
|
||||||
TweeningType::Once,
|
|
||||||
// later to be controlled by speed
|
|
||||||
Duration::from_secs(1),
|
|
||||||
// set the start and end of the animation
|
|
||||||
TransformPositionLens {
|
|
||||||
start: start.extend(0.),
|
|
||||||
end: end.extend(0.),
|
|
||||||
},
|
},
|
||||||
));
|
|
||||||
pos = end;
|
|
||||||
}
|
}
|
||||||
TurtleCommand::Backward(_) => todo!(),
|
|
||||||
TurtleCommand::Left(Angle(x)) => {
|
|
||||||
println!("Adding Left");
|
|
||||||
let start = ori;
|
|
||||||
let end = ori + (*x as f32 * PI / 180.);
|
|
||||||
seq = seq.then(Tween::new(
|
|
||||||
// Use a quadratic easing on both endpoints.
|
|
||||||
EaseFunction::QuadraticInOut,
|
|
||||||
// Loop animation back and forth.
|
|
||||||
TweeningType::Once,
|
|
||||||
// Animation time (one way only; for ping-pong it takes 2 seconds
|
|
||||||
// to come back to start).
|
|
||||||
Duration::from_secs(1),
|
|
||||||
// The lens gives access to the Transform component of the Entity,
|
|
||||||
// for the Animator to animate it. It also contains the start and
|
|
||||||
// end values respectively associated with the progress ratios 0. and 1.
|
|
||||||
TransformRotateZLens { start, end },
|
|
||||||
));
|
|
||||||
ori = end % (2. * PI);
|
|
||||||
}
|
|
||||||
TurtleCommand::Right(_) => todo!(),
|
|
||||||
TurtleCommand::PenUp => todo!(),
|
|
||||||
TurtleCommand::PenDown => todo!(),
|
|
||||||
TurtleCommand::Circle => todo!(),
|
|
||||||
TurtleCommand::Pause => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
seq
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Component, Inspectable)]
|
impl TurtleCommands {
|
||||||
|
pub(crate) fn get_next(&mut self) -> Option<TurtleStep> {
|
||||||
|
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;
|
||||||
|
|
||||||
|
TurtleStep {
|
||||||
|
turtle_animation: None,
|
||||||
|
line_segment: None,
|
||||||
|
line_animation: None,
|
||||||
|
fill: None,
|
||||||
|
stroke: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TurtleCommand::PenDown => {
|
||||||
|
self.state.drawing = true;
|
||||||
|
|
||||||
|
TurtleStep {
|
||||||
|
turtle_animation: None,
|
||||||
|
line_segment: None,
|
||||||
|
line_animation: None,
|
||||||
|
fill: None,
|
||||||
|
stroke: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TurtleCommand::Circle { radius, angle } => {
|
||||||
|
crate::turtle_movement::turtle_circle(&mut self.state, radius.0 as f32, *angle)
|
||||||
|
}
|
||||||
|
TurtleCommand::Pause => todo!(),
|
||||||
|
TurtleCommand::BeginFill => todo!(),
|
||||||
|
TurtleCommand::EndFill => todo!(),
|
||||||
|
};
|
||||||
|
self.state.index = next_index;
|
||||||
|
Some(res)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Default)]
|
||||||
|
pub enum TurtleGraphElement {
|
||||||
|
TurtleLine(TurtleDrawLine),
|
||||||
|
TurtleCircle(TurtleDrawCircle),
|
||||||
|
#[default]
|
||||||
|
Noop,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Component, Reflect)]
|
||||||
pub struct TurtleShape;
|
pub struct TurtleShape;
|
||||||
|
|
||||||
#[derive(Clone, Component, Inspectable)]
|
#[derive(Clone, Component, Reflect, Default)]
|
||||||
pub struct Colors {
|
pub struct Colors {
|
||||||
color: Color,
|
color: Color,
|
||||||
fill_color: Color,
|
fill_color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Inspectable, Default)]
|
#[derive(Component, Reflect, Default)]
|
||||||
pub struct Length(f64);
|
pub enum TurtleCommand {
|
||||||
#[derive(Inspectable, Default)]
|
|
||||||
pub struct Angle(f64);
|
|
||||||
|
|
||||||
#[derive(Component, Inspectable, Default)]
|
|
||||||
enum TurtleCommand {
|
|
||||||
Forward(Length),
|
Forward(Length),
|
||||||
Backward(Length),
|
Backward(Length),
|
||||||
Left(Angle),
|
Left(Angle<f32>),
|
||||||
Right(Angle),
|
Right(Angle<f32>),
|
||||||
PenUp,
|
PenUp,
|
||||||
PenDown,
|
PenDown,
|
||||||
|
BeginFill,
|
||||||
|
EndFill,
|
||||||
#[default]
|
#[default]
|
||||||
Pause,
|
Pause,
|
||||||
Circle,
|
Circle {
|
||||||
|
radius: Length,
|
||||||
|
angle: Angle<f32>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
fn setup(mut commands: Commands) {
|
||||||
let animator = Animator::new(Tween::new(
|
let animator = Animator::new(
|
||||||
// Use a quadratic easing on both endpoints.
|
Tween::new(
|
||||||
EaseFunction::QuadraticInOut,
|
EaseFunction::QuadraticInOut,
|
||||||
// Loop animation back and forth.
|
Duration::from_millis(500),
|
||||||
TweeningType::PingPong,
|
TransformScaleLens {
|
||||||
// Animation time (one way only; for ping-pong it takes 2 seconds
|
start: Vec3::new(1., 1., 0.),
|
||||||
// to come back to start).
|
end: Vec3::new(1.3, 1.3, 0.),
|
||||||
Duration::from_secs(1),
|
|
||||||
// The lens gives access to the Transform component of the Entity,
|
|
||||||
// for the Animator to animate it. It also contains the start and
|
|
||||||
// end values respectively associated with the progress ratios 0. and 1.
|
|
||||||
TransformPositionLens {
|
|
||||||
start: Vec3::ZERO,
|
|
||||||
end: Vec3::new(40., 40., 0.),
|
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
.with_repeat_strategy(bevy_tweening::RepeatStrategy::MirroredRepeat)
|
||||||
|
.with_repeat_count(RepeatCount::Infinite),
|
||||||
|
);
|
||||||
|
commands.spawn(Camera2dBundle::default());
|
||||||
|
let mut turtle_bundle = Turtle::default();
|
||||||
|
let mut tcommands = vec![];
|
||||||
|
//for _ in 0..100 {
|
||||||
|
tcommands.append(&mut paths::geometry_task::geometry_task());
|
||||||
|
//tcommands.append(&mut paths::circle_star::circle_star());
|
||||||
|
//}
|
||||||
|
turtle_bundle.set_commands(tcommands);
|
||||||
|
commands.spawn((
|
||||||
|
turtle_bundle,
|
||||||
|
animator,
|
||||||
|
TurtleShape,
|
||||||
|
Fill::color(Color::MIDNIGHT_BLUE),
|
||||||
|
Stroke::color(Color::BLACK),
|
||||||
));
|
));
|
||||||
commands.spawn_bundle(Camera2dBundle::default());
|
|
||||||
commands
|
|
||||||
.spawn_bundle(Turtle::default())
|
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The sprite is animated by changing its translation depending on the time that has passed since
|
fn draw_lines(
|
||||||
/// the last frame.
|
|
||||||
fn keypresses(
|
|
||||||
//time: Res<Time>,
|
|
||||||
keys: Res<Input<KeyCode>>,
|
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut qry: Query<&mut Animator<Transform>>,
|
mut tcmd: Query<&mut TurtleCommands>,
|
||||||
tcmd: Query<&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();
|
||||||
|
run_animation_step(&mut commands, &mut tcmd, &mut turtle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keypresses(
|
||||||
|
mut commands: Commands,
|
||||||
|
keys: Res<Input<KeyCode>>,
|
||||||
|
mut tcmd: Query<&mut TurtleCommands>,
|
||||||
|
mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>,
|
||||||
) {
|
) {
|
||||||
if keys.just_pressed(KeyCode::W) {
|
if keys.just_pressed(KeyCode::W) {
|
||||||
let tcmd = tcmd.single();
|
let mut tcmd = tcmd.single_mut();
|
||||||
let c = tcmd.generate_tweenable();
|
tcmd.state = TurtleState {
|
||||||
let mut shap = qry.single_mut();
|
start: Vec2::ZERO,
|
||||||
shap.set_tweenable(c);
|
heading: Angle::degrees(0.),
|
||||||
/* commands
|
speed: 1,
|
||||||
.spawn_bundle(Turtle::default())
|
index: 0,
|
||||||
.insert_bundle(GeometryBuilder::build_as(
|
drawing: true,
|
||||||
&turtle_shapes::turtle(),
|
};
|
||||||
DrawMode::Outlined {
|
|
||||||
fill_mode: FillMode::color(Color::RED),
|
run_animation_step(&mut commands, &mut tcmd, &mut turtle);
|
||||||
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
|
|
||||||
},
|
|
||||||
Transform::from_translation(Vec3::new(-100., 0., 0.)),
|
|
||||||
))
|
|
||||||
.insert(Animator::new(Tween::new(
|
|
||||||
// Use a quadratic easing on both endpoints.
|
|
||||||
EaseFunction::QuadraticInOut,
|
|
||||||
// Loop animation back and forth.
|
|
||||||
TweeningType::PingPong,
|
|
||||||
// Animation time (one way only; for ping-pong it takes 2 seconds
|
|
||||||
// to come back to start).
|
|
||||||
Duration::from_secs(1),
|
|
||||||
// The lens gives access to the Transform component of the Entity,
|
|
||||||
// for the Animator to animate it. It also contains the start and
|
|
||||||
// end values respectively associated with the progress ratios 0. and 1.
|
|
||||||
TransformPositionLens {
|
|
||||||
start: Vec3::new(-100., 0., 0.),
|
|
||||||
end: Vec3::new(-140., 40., 0.),
|
|
||||||
},
|
|
||||||
))); */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::prelude::{default, Color, Quat, Transform, Vec2, Vec3};
|
||||||
|
use bevy_prototype_lyon::prelude::{Fill, Path, Stroke};
|
||||||
|
use bevy_tweening::{
|
||||||
|
lens::{TransformPositionLens, TransformRotateZLens},
|
||||||
|
Animator, EaseFunction, Tween,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
datatypes::angle::Angle,
|
||||||
|
primitives::{
|
||||||
|
animation::{CircleAnimationLens, CircleMovementLens, LineAnimationLens},
|
||||||
|
bundles::{TurtleDrawCircle, TurtleDrawLine},
|
||||||
|
},
|
||||||
|
turtle::{Precision, TurtleGraphElement, TurtleState},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TurtleStep {
|
||||||
|
pub turtle_animation: Option<Tween<Transform>>,
|
||||||
|
pub line_segment: Option<TurtleGraphElement>,
|
||||||
|
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 {
|
||||||
|
let start = state.heading;
|
||||||
|
let end = state.heading + angle_to_turn;
|
||||||
|
let animation = Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
Duration::from_millis(state.speed),
|
||||||
|
TransformRotateZLens {
|
||||||
|
start: start.to_radians().value(),
|
||||||
|
end: end.to_radians().value(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_completed_event(state.index as u64);
|
||||||
|
// Don't draw as the position does not change
|
||||||
|
let line = TurtleGraphElement::Noop;
|
||||||
|
// Update the state
|
||||||
|
state.heading = end.limit_smaller_than_full_circle();
|
||||||
|
TurtleStep {
|
||||||
|
turtle_animation: Some(animation),
|
||||||
|
line_segment: Some(line),
|
||||||
|
line_animation: None,
|
||||||
|
fill: None,
|
||||||
|
stroke: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
Duration::from_millis(state.speed),
|
||||||
|
TransformPositionLens {
|
||||||
|
start: start.extend(0.),
|
||||||
|
end: end.extend(0.),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_completed_event(state.index as u64);
|
||||||
|
let line = if state.drawing {
|
||||||
|
TurtleGraphElement::TurtleLine(TurtleDrawLine::new(start, end, state.index))
|
||||||
|
} else {
|
||||||
|
TurtleGraphElement::Noop
|
||||||
|
};
|
||||||
|
let line_animator = Animator::new(Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
Duration::from_millis(state.speed),
|
||||||
|
LineAnimationLens::new(start, end),
|
||||||
|
));
|
||||||
|
state.start = end;
|
||||||
|
TurtleStep {
|
||||||
|
turtle_animation: Some(turtle_movement_animation),
|
||||||
|
line_segment: Some(line),
|
||||||
|
line_animation: Some(line_animator),
|
||||||
|
fill: Some(Fill::color(Color::MIDNIGHT_BLUE)),
|
||||||
|
stroke: Some(Stroke::color(Color::BLACK)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn turtle_circle(
|
||||||
|
state: &mut TurtleState,
|
||||||
|
radius: Precision,
|
||||||
|
angle: Angle<Precision>,
|
||||||
|
) -> 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(
|
||||||
|
((state.heading + left_right).to_radians()).value(),
|
||||||
|
)));
|
||||||
|
|
||||||
|
let turtle_movement_animation = Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
Duration::from_millis(state.speed),
|
||||||
|
CircleMovementLens {
|
||||||
|
start: Transform {
|
||||||
|
translation: state.start.extend(0.),
|
||||||
|
rotation: Quat::from_rotation_z(state.heading.to_radians().value()),
|
||||||
|
scale: Vec3::ONE,
|
||||||
|
},
|
||||||
|
end: angle,
|
||||||
|
center,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_completed_event(state.index as u64);
|
||||||
|
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,
|
||||||
|
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;
|
||||||
|
TurtleStep {
|
||||||
|
turtle_animation: Some(turtle_movement_animation),
|
||||||
|
line_segment: Some(line),
|
||||||
|
line_animation: Some(line_animator),
|
||||||
|
fill: Some(Fill::color(Color::MIDNIGHT_BLUE)),
|
||||||
|
stroke: Some(Stroke::color(Color::BLACK)),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user