Initial turtle-lib commit
Working so far: * display a window * display a turtle * go forward
This commit is contained in:
commit
fac9d218ad
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/Cargo.lock
|
||||||
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "turtle-lib"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
bevy = { version = "0.9" }
|
||||||
|
bevy_prototype_lyon = {version="0.7"}
|
||||||
|
bevy-inspector-egui = "0.14"
|
||||||
|
num-traits = "0.2"
|
||||||
|
bevy_tweening = {version="0.6"}
|
||||||
161
src/commands.rs
Normal file
161
src/commands.rs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
use bevy::prelude::Component;
|
||||||
|
use bevy_inspector_egui::Inspectable;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
drawing::{
|
||||||
|
self,
|
||||||
|
animation::{
|
||||||
|
draw_straight_segment, move_straight_segment, ToAnimationSegment,
|
||||||
|
TurtleAnimationSegment,
|
||||||
|
},
|
||||||
|
TurtleGraphElement,
|
||||||
|
},
|
||||||
|
general::{angle::Angle, length::Length, Coordinate, Precision},
|
||||||
|
state::TurtleState,
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* All the possibilities to draw something with turtle. All the commands can get the position, heading,
|
||||||
|
* color and fill_color from the turtles state.
|
||||||
|
*/
|
||||||
|
#[derive(Component, Inspectable)]
|
||||||
|
pub enum MoveCommand {
|
||||||
|
Forward(Length),
|
||||||
|
Backward(Length),
|
||||||
|
Circle {
|
||||||
|
radius: Length,
|
||||||
|
angle: Angle<Precision>,
|
||||||
|
},
|
||||||
|
Goto(Coordinate),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MoveCommand {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Forward(Length(100.))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Different ways to drop breadcrumbs on the way like a dot or a stamp of the turtles shape.
|
||||||
|
|
||||||
|
#[derive(Component, Inspectable, Default)]
|
||||||
|
pub enum Breadcrumb {
|
||||||
|
Dot,
|
||||||
|
#[default]
|
||||||
|
Stamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Different ways that change the orientation of the turtle.
|
||||||
|
#[derive(Component, Inspectable)]
|
||||||
|
pub enum OrientationCommand {
|
||||||
|
Left(Angle<Precision>),
|
||||||
|
Right(Angle<Precision>),
|
||||||
|
SetHeading,
|
||||||
|
LookAt(Coordinate),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OrientationCommand {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Right(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A combination of all commands that can be used while drawing.
|
||||||
|
#[derive(Component, Inspectable)]
|
||||||
|
pub enum DrawElement {
|
||||||
|
Draw(MoveCommand),
|
||||||
|
Move(MoveCommand),
|
||||||
|
Orient(OrientationCommand),
|
||||||
|
Drip(Breadcrumb),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DrawElement {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Draw(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ToAnimationSegment for DrawElement {
|
||||||
|
fn to_draw_segment(
|
||||||
|
&self,
|
||||||
|
state: &mut TurtleState,
|
||||||
|
) -> crate::drawing::animation::TurtleAnimationSegment {
|
||||||
|
match self {
|
||||||
|
DrawElement::Draw(e) => match e {
|
||||||
|
MoveCommand::Forward(length) => draw_straight_segment(state, length.0),
|
||||||
|
MoveCommand::Backward(length) => draw_straight_segment(state, -length.0),
|
||||||
|
MoveCommand::Circle { radius, angle } => todo!(),
|
||||||
|
MoveCommand::Goto(coord) => todo!(),
|
||||||
|
},
|
||||||
|
DrawElement::Move(e) => match e {
|
||||||
|
MoveCommand::Forward(length) => move_straight_segment(state, length.0),
|
||||||
|
MoveCommand::Backward(length) => move_straight_segment(state, -length.0),
|
||||||
|
MoveCommand::Circle { radius, angle } => todo!(),
|
||||||
|
MoveCommand::Goto(coord) => todo!(),
|
||||||
|
},
|
||||||
|
DrawElement::Orient(_) => todo!(),
|
||||||
|
DrawElement::Drip(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Inspectable)]
|
||||||
|
pub enum TurtleSegment {
|
||||||
|
Single(DrawElement),
|
||||||
|
Outline(Vec<DrawElement>),
|
||||||
|
Filled(Vec<DrawElement>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TurtleSegment {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Single(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ToAnimationSegment for TurtleSegment {
|
||||||
|
fn to_draw_segment(
|
||||||
|
&self,
|
||||||
|
state: &mut TurtleState,
|
||||||
|
) -> crate::drawing::animation::TurtleAnimationSegment {
|
||||||
|
match self {
|
||||||
|
Self::Single(e) => e.to_draw_segment(state),
|
||||||
|
Self::Outline(_) => todo!(),
|
||||||
|
Self::Filled(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Component, Inspectable)]
|
||||||
|
pub struct TurtleCommands {
|
||||||
|
animation_state: usize,
|
||||||
|
commands: Vec<TurtleSegment>,
|
||||||
|
lines: Vec<TurtleGraphElement>,
|
||||||
|
state: TurtleState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TurtleCommands {
|
||||||
|
pub fn new(commands: Vec<TurtleSegment>) -> Self {
|
||||||
|
let mut state = TurtleState::default();
|
||||||
|
state.set_speed(200);
|
||||||
|
Self {
|
||||||
|
animation_state: 0,
|
||||||
|
commands,
|
||||||
|
lines: vec![],
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn push(&mut self, segment: TurtleSegment) {
|
||||||
|
self.commands.push(segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for TurtleCommands {
|
||||||
|
type Item = TurtleAnimationSegment;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let index = self.animation_state;
|
||||||
|
let next_index = index + 1;
|
||||||
|
|
||||||
|
if let Some(command) = self.commands.get(self.animation_state) {
|
||||||
|
let res = command.to_draw_segment(&mut self.state);
|
||||||
|
self.animation_state = next_index;
|
||||||
|
Some(res)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/debug.rs
Normal file
12
src/debug.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use bevy::prelude::Plugin;
|
||||||
|
use bevy_inspector_egui::WorldInspectorPlugin;
|
||||||
|
|
||||||
|
pub struct DebugPlugin;
|
||||||
|
|
||||||
|
impl Plugin for DebugPlugin {
|
||||||
|
fn build(&self, app: &mut bevy::prelude::App) {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
app.add_plugin(WorldInspectorPlugin::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/drawing.rs
Normal file
15
src/drawing.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use bevy_inspector_egui::Inspectable;
|
||||||
|
|
||||||
|
pub use self::line_segments::{TurtleDrawCircle, TurtleDrawLine};
|
||||||
|
|
||||||
|
pub mod animation;
|
||||||
|
mod line_segments;
|
||||||
|
pub(crate) mod run_step;
|
||||||
|
|
||||||
|
#[derive(Inspectable, Default)]
|
||||||
|
pub enum TurtleGraphElement {
|
||||||
|
TurtleLine(TurtleDrawLine),
|
||||||
|
TurtleCircle(TurtleDrawCircle),
|
||||||
|
#[default]
|
||||||
|
Noop,
|
||||||
|
}
|
||||||
204
src/drawing/animation.rs
Normal file
204
src/drawing/animation.rs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
mod circle_lens;
|
||||||
|
mod line_lens;
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
prelude::{Quat, Transform, Vec2, Vec3},
|
||||||
|
render::render_resource::encase::rts_array::Length,
|
||||||
|
};
|
||||||
|
use bevy_prototype_lyon::prelude::Path;
|
||||||
|
use bevy_tweening::{
|
||||||
|
lens::{TransformPositionLens, TransformRotateZLens},
|
||||||
|
Animator, EaseFunction, RepeatCount, RepeatStrategy, Tween,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
general::{angle::Angle, Coordinate, Precision},
|
||||||
|
state::TurtleState,
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::{
|
||||||
|
circle_lens::{CircleAnimationLens, CircleMovementLens},
|
||||||
|
line_lens::LineAnimationLens,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{TurtleDrawCircle, TurtleDrawLine, TurtleGraphElement};
|
||||||
|
|
||||||
|
pub struct TurtleAnimationSegment {
|
||||||
|
pub turtle_animation: Option<Tween<Transform>>,
|
||||||
|
pub line_segment: Option<TurtleGraphElement>,
|
||||||
|
pub line_animation: Option<Animator<Path>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ToAnimationSegment {
|
||||||
|
fn to_draw_segment(
|
||||||
|
&self,
|
||||||
|
state: &mut TurtleState,
|
||||||
|
) -> crate::drawing::animation::TurtleAnimationSegment;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn turtle_turn(
|
||||||
|
state: &mut TurtleState,
|
||||||
|
angle_to_turn: Angle<Precision>,
|
||||||
|
) -> TurtleAnimationSegment {
|
||||||
|
let start = state.heading();
|
||||||
|
let end = state.heading() + angle_to_turn;
|
||||||
|
let animation = Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
state.animation_duration(),
|
||||||
|
TransformRotateZLens {
|
||||||
|
start: start.to_radians().value(),
|
||||||
|
end: end.to_radians().value(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_completed_event(state.segment_index() as u64);
|
||||||
|
// Don't draw as the position does not change
|
||||||
|
let line = TurtleGraphElement::Noop;
|
||||||
|
// Update the state
|
||||||
|
state.set_heading(end.limit_smaller_than_full_circle());
|
||||||
|
TurtleAnimationSegment {
|
||||||
|
turtle_animation: Some(animation),
|
||||||
|
line_segment: Some(line),
|
||||||
|
line_animation: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_straight_segment(state: &mut TurtleState, length: Precision) -> TurtleAnimationSegment {
|
||||||
|
let animation = MoveStraightTurtleAnimation::new(state, length);
|
||||||
|
|
||||||
|
state.set_position(animation.end);
|
||||||
|
TurtleAnimationSegment {
|
||||||
|
turtle_animation: Some(animation.animation),
|
||||||
|
line_segment: None,
|
||||||
|
line_animation: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_straight_segment(state: &mut TurtleState, length: Precision) -> TurtleAnimationSegment {
|
||||||
|
let animation = MoveStraightTurtleAnimation::new(state, length);
|
||||||
|
let line_animation = MoveStraightLineAnimation::new(state, length, &animation);
|
||||||
|
|
||||||
|
state.set_position(animation.end);
|
||||||
|
TurtleAnimationSegment {
|
||||||
|
turtle_animation: Some(animation.animation),
|
||||||
|
line_segment: Some(TurtleGraphElement::TurtleLine(line_animation.line)),
|
||||||
|
line_animation: Some(Animator::new(line_animation.animation)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MoveStraightLineAnimation {
|
||||||
|
start: Coordinate,
|
||||||
|
end: Coordinate,
|
||||||
|
line: TurtleDrawLine,
|
||||||
|
animation: Tween<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MoveStraightLineAnimation {
|
||||||
|
fn new(
|
||||||
|
state: &TurtleState,
|
||||||
|
length: Precision,
|
||||||
|
turtle_animation: &MoveStraightTurtleAnimation,
|
||||||
|
) -> Self {
|
||||||
|
let line = TurtleDrawLine::new(turtle_animation.start, turtle_animation.end);
|
||||||
|
let line_animation = Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
state.animation_duration(),
|
||||||
|
LineAnimationLens::new(turtle_animation.start, turtle_animation.end),
|
||||||
|
)
|
||||||
|
/* .with_repeat_strategy(RepeatStrategy::MirroredRepeat)
|
||||||
|
.with_repeat_count(RepeatCount::Infinite)*/;
|
||||||
|
Self {
|
||||||
|
start: turtle_animation.start,
|
||||||
|
end: turtle_animation.end,
|
||||||
|
line,
|
||||||
|
animation: line_animation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MoveStraightTurtleAnimation {
|
||||||
|
start: Coordinate,
|
||||||
|
end: Coordinate,
|
||||||
|
animation: Tween<Transform>,
|
||||||
|
}
|
||||||
|
impl MoveStraightTurtleAnimation {
|
||||||
|
fn new(state: &TurtleState, length: Precision) -> Self {
|
||||||
|
let start = state.position();
|
||||||
|
let end =
|
||||||
|
state.position() + (Vec2::from_angle(state.heading().to_radians().value()) * length);
|
||||||
|
let turtle_movement_animation = Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
state.animation_duration(),
|
||||||
|
TransformPositionLens {
|
||||||
|
start: start.extend(0.),
|
||||||
|
end: end.extend(0.),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_completed_event(state.segment_index() as u64);
|
||||||
|
Self {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
animation: turtle_movement_animation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn turtle_circle(
|
||||||
|
state: &mut TurtleState,
|
||||||
|
radius: Precision,
|
||||||
|
angle: Angle<Precision>,
|
||||||
|
) -> TurtleAnimationSegment {
|
||||||
|
let radii = Vec2::ONE * radius.abs();
|
||||||
|
let left_right = Angle::degrees(if radius >= 0. { 90. } else { -90. });
|
||||||
|
let center = state.position()
|
||||||
|
+ (Vec2::new(radius.abs(), 0.).rotate(Vec2::from_angle(
|
||||||
|
((state.heading() + left_right).to_radians()).value(),
|
||||||
|
)));
|
||||||
|
|
||||||
|
let turtle_movement_animation = Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
state.animation_duration(),
|
||||||
|
CircleMovementLens {
|
||||||
|
start: Transform {
|
||||||
|
translation: state.position().extend(0.),
|
||||||
|
rotation: Quat::from_rotation_z(state.heading().to_radians().value()),
|
||||||
|
scale: Vec3::ONE,
|
||||||
|
},
|
||||||
|
end: angle,
|
||||||
|
center,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_completed_event(state.segment_index());
|
||||||
|
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.position(),
|
||||||
|
end_pos,
|
||||||
|
))
|
||||||
|
/* } else {
|
||||||
|
TurtleGraphElement::Noop
|
||||||
|
} */;
|
||||||
|
let line_animator = Animator::new(Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
state.animation_duration(),
|
||||||
|
CircleAnimationLens {
|
||||||
|
start_pos: state.position(),
|
||||||
|
center,
|
||||||
|
radii,
|
||||||
|
start: Angle::degrees(0.),
|
||||||
|
end: angle,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
state.set_position(end_pos);
|
||||||
|
state.set_heading(state.heading() + angle);
|
||||||
|
TurtleAnimationSegment {
|
||||||
|
turtle_animation: Some(turtle_movement_animation),
|
||||||
|
line_segment: Some(line),
|
||||||
|
line_animation: Some(line_animator),
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/drawing/animation/circle_lens.rs
Normal file
51
src/drawing/animation/circle_lens.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use bevy::prelude::{Quat, Transform, Vec2};
|
||||||
|
use bevy_prototype_lyon::prelude::{Path, PathBuilder, ShapePath};
|
||||||
|
use bevy_tweening::Lens;
|
||||||
|
|
||||||
|
use crate::general::{angle::Angle, Precision};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/drawing/animation/line_lens.rs
Normal file
24
src/drawing/animation/line_lens.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use bevy::prelude::Vec2;
|
||||||
|
use bevy_prototype_lyon::{
|
||||||
|
prelude::{Path, ShapePath},
|
||||||
|
shapes,
|
||||||
|
};
|
||||||
|
use bevy_tweening::Lens;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/drawing/line_segments.rs
Normal file
82
src/drawing/line_segments.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use bevy::prelude::{Bundle, Color, Component, Name, Transform, Vec2};
|
||||||
|
use bevy_inspector_egui::Inspectable;
|
||||||
|
use bevy_prototype_lyon::{
|
||||||
|
entity::ShapeBundle,
|
||||||
|
prelude::{DrawMode, FillMode, GeometryBuilder, PathBuilder, StrokeMode},
|
||||||
|
shapes::Line,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::general::{angle::Angle, Precision};
|
||||||
|
|
||||||
|
#[derive(Bundle, Inspectable, Default)]
|
||||||
|
pub struct TurtleDrawLine {
|
||||||
|
#[inspectable(ignore)]
|
||||||
|
line: ShapeBundle,
|
||||||
|
name: Name,
|
||||||
|
marker: LineMarker,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Default, Inspectable)]
|
||||||
|
struct LineMarker;
|
||||||
|
|
||||||
|
impl TurtleDrawLine {
|
||||||
|
pub(crate) fn new(start: Vec2, end: Vec2) -> Self {
|
||||||
|
Self {
|
||||||
|
line: GeometryBuilder::build_as(
|
||||||
|
&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 {}-{}", start, end)),
|
||||||
|
marker: LineMarker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Bundle, Inspectable, Default)]
|
||||||
|
|
||||||
|
pub struct TurtleDrawCircle {
|
||||||
|
#[inspectable(ignore)]
|
||||||
|
line: ShapeBundle,
|
||||||
|
name: Name,
|
||||||
|
marker: CircleMarker,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Default, Inspectable)]
|
||||||
|
struct CircleMarker;
|
||||||
|
|
||||||
|
impl TurtleDrawCircle {
|
||||||
|
pub(crate) fn new(
|
||||||
|
center: Vec2,
|
||||||
|
radii: Vec2,
|
||||||
|
angle: Angle<Precision>,
|
||||||
|
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);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
line: GeometryBuilder::build_as(
|
||||||
|
&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 at {}, {}", center.x, center.y)),
|
||||||
|
marker: CircleMarker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/drawing/run_step.rs
Normal file
58
src/drawing/run_step.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use bevy::prelude::{Commands, Query, Transform, With};
|
||||||
|
use bevy_tweening::Animator;
|
||||||
|
|
||||||
|
use crate::{commands::TurtleCommands, shapes::TurtleShape};
|
||||||
|
|
||||||
|
use super::{animation::TurtleAnimationSegment, TurtleGraphElement};
|
||||||
|
|
||||||
|
pub fn run_animation_step(
|
||||||
|
commands: &mut Commands,
|
||||||
|
tcmd: &mut TurtleCommands,
|
||||||
|
turtle: &mut Query<&mut Animator<Transform>, With<TurtleShape>>,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
match tcmd.next() {
|
||||||
|
Some(TurtleAnimationSegment {
|
||||||
|
turtle_animation: Some(turtle_animation),
|
||||||
|
line_segment: Some(graph_element_to_draw),
|
||||||
|
line_animation: Some(line_animation),
|
||||||
|
}) => {
|
||||||
|
let mut turtle = turtle.single_mut();
|
||||||
|
turtle.set_tweenable(turtle_animation);
|
||||||
|
match graph_element_to_draw {
|
||||||
|
TurtleGraphElement::TurtleLine(line) => {
|
||||||
|
commands.spawn((line, line_animation));
|
||||||
|
}
|
||||||
|
TurtleGraphElement::Noop => (),
|
||||||
|
TurtleGraphElement::TurtleCircle(circle) => {
|
||||||
|
commands.spawn((circle, line_animation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// In case a rotation is performed the line drawing can be skipped
|
||||||
|
Some(TurtleAnimationSegment {
|
||||||
|
turtle_animation: Some(turtle_animation),
|
||||||
|
line_segment: Some(_),
|
||||||
|
line_animation: None,
|
||||||
|
})|
|
||||||
|
// In case a rotation is performed the line drawing can be skipped
|
||||||
|
Some(TurtleAnimationSegment {
|
||||||
|
turtle_animation: Some(turtle_animation),
|
||||||
|
line_segment: None,
|
||||||
|
line_animation: None,
|
||||||
|
}) => {
|
||||||
|
let mut turtle = turtle.single_mut();
|
||||||
|
turtle.set_tweenable(turtle_animation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(_e) => {
|
||||||
|
println!("without animation");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("nothing to draw");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/events.rs
Normal file
3
src/events.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
use bevy::prelude::Entity;
|
||||||
|
|
||||||
|
pub struct DrawingStartedEvent(pub Entity);
|
||||||
9
src/general.rs
Normal file
9
src/general.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
use bevy::prelude::Vec2;
|
||||||
|
|
||||||
|
pub mod angle;
|
||||||
|
pub mod length;
|
||||||
|
|
||||||
|
pub type Precision = f32;
|
||||||
|
pub type Coordinate = Vec2;
|
||||||
|
pub type Visibility = bool;
|
||||||
|
pub type Speed = u32;
|
||||||
195
src/general/angle.rs
Normal file
195
src/general/angle.rs
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
use bevy_inspector_egui::Inspectable;
|
||||||
|
use std::{
|
||||||
|
f32::consts::PI,
|
||||||
|
ops::{Add, Div, Mul, Neg, Rem, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Precision;
|
||||||
|
|
||||||
|
#[derive(Inspectable, 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(Inspectable, 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);
|
||||||
|
}
|
||||||
6
src/general/length.rs
Normal file
6
src/general/length.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use bevy_inspector_egui::Inspectable;
|
||||||
|
|
||||||
|
use super::Precision;
|
||||||
|
|
||||||
|
#[derive(Inspectable, Default, Copy, Clone, Debug)]
|
||||||
|
pub struct Length(pub Precision);
|
||||||
118
src/lib.rs
Normal file
118
src/lib.rs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*, window::close_on_esc};
|
||||||
|
use bevy_inspector_egui::RegisterInspectable;
|
||||||
|
use bevy_prototype_lyon::prelude::{Path, ShapePlugin};
|
||||||
|
use bevy_tweening::{
|
||||||
|
component_animator_system, lens::TransformScaleLens, Animator, EaseFunction, Tween,
|
||||||
|
TweenCompleted, TweeningPlugin,
|
||||||
|
};
|
||||||
|
use events::DrawingStartedEvent;
|
||||||
|
use shapes::{TurtleColors, TurtleShape};
|
||||||
|
use turtle_bundle::{AnimatedTurtle, TurtleBundle};
|
||||||
|
|
||||||
|
pub use commands::TurtleCommands;
|
||||||
|
|
||||||
|
mod commands;
|
||||||
|
mod debug;
|
||||||
|
mod drawing;
|
||||||
|
pub mod events;
|
||||||
|
mod general;
|
||||||
|
pub mod shapes;
|
||||||
|
mod state;
|
||||||
|
mod turtle_bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
The turtle plugin is the core of this turtle module.
|
||||||
|
|
||||||
|
In order to facilitate the setup this plugin also inserts the `DefaultPlugins` and many other things.
|
||||||
|
|
||||||
|
Before using any of the functions add this plugin using:
|
||||||
|
```rust
|
||||||
|
|
||||||
|
app::new().add_plugin(turtle_lib::TurtlePlugin)
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
pub struct TurtlePlugin;
|
||||||
|
|
||||||
|
impl Plugin for TurtlePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
|
window: WindowDescriptor {
|
||||||
|
title: "Immigration Game".to_string(),
|
||||||
|
width: 1200.,
|
||||||
|
height: 800.,
|
||||||
|
present_mode: bevy::window::PresentMode::Fifo, // vsync
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
}))
|
||||||
|
.add_plugin(debug::DebugPlugin)
|
||||||
|
.add_plugin(ShapePlugin)
|
||||||
|
.add_plugin(TweeningPlugin)
|
||||||
|
.add_event::<DrawingStartedEvent>()
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(keypresses)
|
||||||
|
.add_system(component_animator_system::<Path>)
|
||||||
|
.add_system(close_on_esc)
|
||||||
|
.add_system(draw_lines)
|
||||||
|
.register_inspectable::<TurtleColors>()
|
||||||
|
.register_inspectable::<TurtleCommands>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn(Camera2dBundle {
|
||||||
|
camera_2d: Camera2d {
|
||||||
|
clear_color: ClearColorConfig::Custom(Color::BEIGE),
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_a_turtle() -> AnimatedTurtle {
|
||||||
|
let animator = Animator::new(Tween::new(
|
||||||
|
EaseFunction::QuadraticInOut,
|
||||||
|
Duration::from_millis(3000),
|
||||||
|
TransformScaleLens {
|
||||||
|
start: Vec3::ZERO,
|
||||||
|
end: Vec3::ONE * 1.3,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
let turtle_bundle = TurtleBundle::default();
|
||||||
|
AnimatedTurtle {
|
||||||
|
animator,
|
||||||
|
turtle_bundle,
|
||||||
|
turtle_shape: TurtleShape,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keypresses(
|
||||||
|
mut commands: Commands,
|
||||||
|
keys: Res<Input<KeyCode>>,
|
||||||
|
mut tcmd: Query<(Entity, &mut TurtleCommands)>,
|
||||||
|
mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>,
|
||||||
|
mut ev_start: EventWriter<DrawingStartedEvent>,
|
||||||
|
) {
|
||||||
|
if keys.just_pressed(KeyCode::W) {
|
||||||
|
for (entity, mut tcmd) in tcmd.iter_mut() {
|
||||||
|
crate::drawing::run_step::run_animation_step(&mut commands, &mut tcmd, &mut turtle);
|
||||||
|
ev_start.send(DrawingStartedEvent(entity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_lines(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut tcmd: Query<&mut TurtleCommands>,
|
||||||
|
mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>,
|
||||||
|
mut query_event: EventReader<TweenCompleted>, // TODO: howto attach only to the right event?
|
||||||
|
) {
|
||||||
|
for _ev in query_event.iter() {
|
||||||
|
if let Ok(mut tcmd) = tcmd.get_single_mut() {
|
||||||
|
crate::drawing::run_step::run_animation_step(&mut commands, &mut tcmd, &mut turtle)
|
||||||
|
} else {
|
||||||
|
println!("Failed to get the turtle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/shapes.rs
Normal file
13
src/shapes.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
mod turtle;
|
||||||
|
use bevy::prelude::{Color, Component};
|
||||||
|
use bevy_inspector_egui::Inspectable;
|
||||||
|
pub use turtle::turtle;
|
||||||
|
|
||||||
|
#[derive(Clone, Component, Inspectable)]
|
||||||
|
pub struct TurtleShape;
|
||||||
|
|
||||||
|
#[derive(Clone, Component, Inspectable, Default)]
|
||||||
|
pub struct TurtleColors {
|
||||||
|
color: Color,
|
||||||
|
fill_color: Color,
|
||||||
|
}
|
||||||
46
src/shapes/turtle.rs
Normal file
46
src/shapes/turtle.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use bevy::prelude::Vec2;
|
||||||
|
use bevy_prototype_lyon::prelude::{Path, PathBuilder};
|
||||||
|
|
||||||
|
use crate::general::Precision;
|
||||||
|
|
||||||
|
pub fn turtle() -> Path {
|
||||||
|
let polygon: &[[Precision; 2]; 23] = &[
|
||||||
|
[-2.5, 14.0],
|
||||||
|
[-1.25, 10.0],
|
||||||
|
[-4.0, 7.0],
|
||||||
|
[-7.0, 9.0],
|
||||||
|
[-9.0, 8.0],
|
||||||
|
[-6.0, 5.0],
|
||||||
|
[-7.0, 1.0],
|
||||||
|
[-5.0, -3.0],
|
||||||
|
[-8.0, -6.0],
|
||||||
|
[-6.0, -8.0],
|
||||||
|
[-4.0, -5.0],
|
||||||
|
[0.0, -7.0],
|
||||||
|
[4.0, -5.0],
|
||||||
|
[6.0, -8.0],
|
||||||
|
[8.0, -6.0],
|
||||||
|
[5.0, -3.0],
|
||||||
|
[7.0, 1.0],
|
||||||
|
[6.0, 5.0],
|
||||||
|
[9.0, 8.0],
|
||||||
|
[7.0, 9.0],
|
||||||
|
[4.0, 7.0],
|
||||||
|
[1.25, 10.0],
|
||||||
|
[2.5, 14.0],
|
||||||
|
];
|
||||||
|
let mut turtle_path = PathBuilder::new();
|
||||||
|
turtle_path.line_to(Vec2::new(1.0, 1.0));
|
||||||
|
turtle_path.line_to(Vec2::new(-1.0, 1.0));
|
||||||
|
turtle_path.line_to(Vec2::new(-1.0, -1.0));
|
||||||
|
turtle_path.line_to(Vec2::new(1.0, -1.0));
|
||||||
|
turtle_path.close();
|
||||||
|
turtle_path.move_to(Vec2::new(0.0, 16.0).rotate(Vec2::from_angle(-PI / 2.)));
|
||||||
|
for coord in polygon {
|
||||||
|
turtle_path.line_to(Vec2::from_array(*coord).rotate(Vec2::from_angle(-PI / 2.)));
|
||||||
|
}
|
||||||
|
turtle_path.close();
|
||||||
|
turtle_path.build()
|
||||||
|
}
|
||||||
69
src/state.rs
Normal file
69
src/state.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use std::{cmp::max, time::Duration};
|
||||||
|
|
||||||
|
use bevy::prelude::{Color, Component, Transform};
|
||||||
|
use bevy_inspector_egui::Inspectable;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commands::TurtleSegment,
|
||||||
|
general::{angle::Angle, Coordinate, Precision, Speed, Visibility},
|
||||||
|
shapes::TurtleColors,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Describing the full state of a turtle.
|
||||||
|
#[derive(Component, Inspectable, Default)]
|
||||||
|
pub struct TurtleState {
|
||||||
|
drawing: Vec<TurtleSegment>,
|
||||||
|
position: Coordinate,
|
||||||
|
heading: Angle<Precision>,
|
||||||
|
colors: TurtleColors,
|
||||||
|
visible: Visibility,
|
||||||
|
shape_transform: Transform,
|
||||||
|
speed: Speed,
|
||||||
|
segment_index: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TurtleState {
|
||||||
|
pub fn add_segment(&mut self, seg: TurtleSegment) {
|
||||||
|
self.drawing.push(seg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TurtleState {
|
||||||
|
pub fn segment_index(&self) -> u64 {
|
||||||
|
self.segment_index
|
||||||
|
}
|
||||||
|
pub fn heading(&self) -> Angle<Precision> {
|
||||||
|
self.heading
|
||||||
|
}
|
||||||
|
pub fn position(&self) -> Coordinate {
|
||||||
|
self.position
|
||||||
|
}
|
||||||
|
pub fn speed(&self) -> Speed {
|
||||||
|
self.speed
|
||||||
|
}
|
||||||
|
/// The duration of animations calculated from the speed.
|
||||||
|
pub fn animation_duration(&self) -> Duration {
|
||||||
|
Duration::from_millis(self.speed() as u64)
|
||||||
|
}
|
||||||
|
pub fn shape_transform(&self) -> Transform {
|
||||||
|
self.shape_transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TurtleState {
|
||||||
|
pub fn set_heading(&mut self, angle: Angle<Precision>) -> &mut Self {
|
||||||
|
self.heading = angle;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_position(&mut self, position: Coordinate) -> &mut Self {
|
||||||
|
self.position = position;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_speed(&mut self, speed: Speed) -> &mut Self {
|
||||||
|
self.speed = max(speed, 1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_shape_transform(&mut self, transform: Transform) -> &mut Self {
|
||||||
|
self.shape_transform = transform;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/turtle_bundle.rs
Normal file
75
src/turtle_bundle.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use bevy::prelude::{Bundle, Color, Name, Transform};
|
||||||
|
use bevy_prototype_lyon::{
|
||||||
|
entity::ShapeBundle,
|
||||||
|
prelude::{DrawMode, FillMode, GeometryBuilder, StrokeMode},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commands::{DrawElement, MoveCommand, TurtleCommands, TurtleSegment},
|
||||||
|
general::length::Length,
|
||||||
|
shapes::{self, TurtleColors},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Bundle)]
|
||||||
|
pub struct TurtleBundle {
|
||||||
|
colors: TurtleColors,
|
||||||
|
pub commands: TurtleCommands,
|
||||||
|
name: Name,
|
||||||
|
shape: ShapeBundle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TurtleBundle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
colors: TurtleColors::default(),
|
||||||
|
commands: TurtleCommands::new(vec![]),
|
||||||
|
name: Name::new("Turtle"),
|
||||||
|
shape: GeometryBuilder::build_as(
|
||||||
|
&shapes::turtle(),
|
||||||
|
DrawMode::Outlined {
|
||||||
|
fill_mode: FillMode::color(Color::MIDNIGHT_BLUE),
|
||||||
|
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
|
||||||
|
},
|
||||||
|
Transform::IDENTITY,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TurtleBundle {
|
||||||
|
pub fn set_commands(&mut self, commands: Vec<TurtleSegment>) {
|
||||||
|
self.commands = TurtleCommands::new(commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TurtleBundle {
|
||||||
|
pub fn forward(&mut self, len: f32) -> &mut Self {
|
||||||
|
self.commands.push(TurtleSegment::Single(DrawElement::Draw(
|
||||||
|
MoveCommand::Forward(Length(len)),
|
||||||
|
)));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Bundle)]
|
||||||
|
pub struct AnimatedTurtle {
|
||||||
|
pub animator: bevy_tweening::Animator<bevy::prelude::Transform>,
|
||||||
|
pub turtle_bundle: TurtleBundle,
|
||||||
|
pub turtle_shape: shapes::TurtleShape,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AnimatedTurtle {
|
||||||
|
type Target = TurtleBundle;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.turtle_bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for AnimatedTurtle {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.turtle_bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user