Merge remote-tracking branch 'sub/main'
This commit is contained in:
commit
da7b9493ec
15
turtle-lib/Cargo.toml
Normal file
15
turtle-lib/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.16"
|
||||
num-traits = "0.2"
|
||||
bevy_tweening = {version="0.6"}
|
||||
9
turtle-lib/LICENSE
Normal file
9
turtle-lib/LICENSE
Normal file
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
255
turtle-lib/src/builders.rs
Normal file
255
turtle-lib/src/builders.rs
Normal file
@ -0,0 +1,255 @@
|
||||
use std::ops::Neg;
|
||||
|
||||
use crate::{
|
||||
commands::{DrawElement, MoveCommand, TurtleSegment},
|
||||
general::{angle::Angle, length::Length, Precision},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct TurtlePlan {
|
||||
/**
|
||||
* A turtle Plan contains the segments of a turtle drawing.
|
||||
* The segments in turn contain the commands to draw the graph.
|
||||
*/
|
||||
commands: Vec<TurtleSegment>,
|
||||
}
|
||||
|
||||
pub trait WithCommands {
|
||||
fn get_mut_commands(&mut self) -> &mut Vec<TurtleSegment>;
|
||||
fn get_commands(self) -> Vec<TurtleSegment>;
|
||||
}
|
||||
|
||||
impl WithCommands for TurtlePlan {
|
||||
fn get_mut_commands(&mut self) -> &mut Vec<TurtleSegment> {
|
||||
&mut self.commands
|
||||
}
|
||||
|
||||
fn get_commands(self) -> Vec<TurtleSegment> {
|
||||
self.commands
|
||||
}
|
||||
}
|
||||
|
||||
impl TurtlePlan {
|
||||
pub fn new() -> TurtlePlan {
|
||||
TurtlePlan { commands: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DirectionalMovement: WithCommands {
|
||||
fn forward<IntoDistance>(&mut self, length: IntoDistance) -> &mut Self
|
||||
where
|
||||
Length: From<IntoDistance>,
|
||||
{
|
||||
let length: Length = length.into();
|
||||
self.get_mut_commands()
|
||||
.push(TurtleSegment::Single(DrawElement::Draw(
|
||||
crate::commands::MoveCommand::Forward(length),
|
||||
)));
|
||||
self
|
||||
}
|
||||
fn backward<IntoDistance>(&mut self, length: IntoDistance) -> &mut Self
|
||||
where
|
||||
Length: From<IntoDistance>,
|
||||
{
|
||||
let length: Length = length.into();
|
||||
self.get_mut_commands()
|
||||
.push(TurtleSegment::Single(DrawElement::Draw(
|
||||
crate::commands::MoveCommand::Backward(length),
|
||||
)));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectionalMovement for TurtlePlan {}
|
||||
|
||||
pub trait Turnable: WithCommands {
|
||||
fn right<IntoAngle>(&mut self, angle: IntoAngle) -> &mut Self
|
||||
where
|
||||
Angle<Precision>: From<IntoAngle>,
|
||||
{
|
||||
let angle: Angle<Precision> = angle.into();
|
||||
self.get_mut_commands()
|
||||
.push(TurtleSegment::Single(DrawElement::Orient(
|
||||
crate::commands::OrientationCommand::Right(angle),
|
||||
)));
|
||||
self
|
||||
}
|
||||
fn left<IntoAngle>(&mut self, angle: IntoAngle) -> &mut Self
|
||||
where
|
||||
Angle<Precision>: From<IntoAngle>,
|
||||
{
|
||||
let angle: Angle<Precision> = angle.into();
|
||||
self.get_mut_commands()
|
||||
.push(TurtleSegment::Single(DrawElement::Orient(
|
||||
crate::commands::OrientationCommand::Left(angle),
|
||||
)));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Turnable for TurtlePlan {}
|
||||
|
||||
pub trait CurvedMovement: WithCommands {
|
||||
fn circle<IntoAngle, IntoDistance>(
|
||||
&mut self,
|
||||
radius: IntoDistance,
|
||||
extend: IntoAngle,
|
||||
) -> &mut Self
|
||||
where
|
||||
Angle<Precision>: From<IntoAngle>,
|
||||
Length: From<IntoDistance>,
|
||||
{
|
||||
let angle: Angle<Precision> = extend.into();
|
||||
let radius: Length = radius.into();
|
||||
self.get_mut_commands()
|
||||
.push(TurtleSegment::Single(DrawElement::Draw(
|
||||
MoveCommand::Circle { radius, angle },
|
||||
)));
|
||||
self
|
||||
}
|
||||
fn circle_right<IntoAngle, IntoDistance: Neg + Neg<Output = IntoDistance>>(
|
||||
&mut self,
|
||||
radius: IntoDistance,
|
||||
extend: IntoAngle,
|
||||
) -> &mut Self
|
||||
where
|
||||
Angle<Precision>: From<IntoAngle>,
|
||||
Length: From<IntoDistance>,
|
||||
{
|
||||
self.circle(-radius, extend);
|
||||
println!("Warning: circle with right arc not working yet...");
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl CurvedMovement for TurtlePlan {}
|
||||
|
||||
pub trait StopLine<T>
|
||||
where
|
||||
T: WithCommands,
|
||||
{
|
||||
fn pen_up(self) -> InvisibleLinesPlan<T>;
|
||||
}
|
||||
|
||||
impl StopLine<TurtlePlan> for TurtlePlan {
|
||||
fn pen_up(self) -> InvisibleLinesPlan<TurtlePlan> {
|
||||
{
|
||||
InvisibleLinesPlan {
|
||||
before: self,
|
||||
commands: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StartLine<T> {
|
||||
fn pen_down(self) -> T;
|
||||
}
|
||||
|
||||
impl<T> StartLine<T> for InvisibleLinesPlan<T>
|
||||
where
|
||||
T: WithCommands,
|
||||
{
|
||||
fn pen_down(mut self) -> T {
|
||||
self.before.get_mut_commands().append(&mut self.commands);
|
||||
self.before
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InvisibleLinesPlan<T: WithCommands> {
|
||||
before: T,
|
||||
commands: Vec<TurtleSegment>,
|
||||
}
|
||||
|
||||
impl<T> WithCommands for InvisibleLinesPlan<T>
|
||||
where
|
||||
T: WithCommands,
|
||||
{
|
||||
fn get_mut_commands(&mut self) -> &mut Vec<TurtleSegment> {
|
||||
&mut self.commands
|
||||
}
|
||||
|
||||
fn get_commands(self) -> Vec<TurtleSegment> {
|
||||
self.commands
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> InvisibleLinesPlan<T>
|
||||
where
|
||||
T: WithCommands,
|
||||
{
|
||||
pub fn new(before: T) -> Self {
|
||||
InvisibleLinesPlan {
|
||||
before,
|
||||
commands: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Turnable for InvisibleLinesPlan<TurtlePlan> {}
|
||||
|
||||
impl<T> DirectionalMovement for InvisibleLinesPlan<T>
|
||||
where
|
||||
T: WithCommands,
|
||||
{
|
||||
fn forward<IntoDistance>(&mut self, length: IntoDistance) -> &mut Self
|
||||
where
|
||||
Length: From<IntoDistance>,
|
||||
{
|
||||
let length: Length = length.into();
|
||||
self.get_mut_commands()
|
||||
.push(TurtleSegment::Single(DrawElement::Move(
|
||||
crate::commands::MoveCommand::Forward(length),
|
||||
)));
|
||||
self
|
||||
}
|
||||
|
||||
fn backward<IntoDistance>(&mut self, length: IntoDistance) -> &mut Self
|
||||
where
|
||||
Length: From<IntoDistance>,
|
||||
{
|
||||
let length: Length = length.into();
|
||||
self.get_mut_commands()
|
||||
.push(TurtleSegment::Single(DrawElement::Move(
|
||||
crate::commands::MoveCommand::Backward(length),
|
||||
)));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CurvedMovement for InvisibleLinesPlan<T>
|
||||
where
|
||||
T: WithCommands,
|
||||
{
|
||||
fn circle<IntoAngle, IntoDistance>(
|
||||
&mut self,
|
||||
radius: IntoDistance,
|
||||
extend: IntoAngle,
|
||||
) -> &mut Self
|
||||
where
|
||||
Angle<Precision>: From<IntoAngle>,
|
||||
Length: From<IntoDistance>,
|
||||
{
|
||||
let angle: Angle<Precision> = extend.into();
|
||||
let radius: Length = radius.into();
|
||||
self.get_mut_commands()
|
||||
.push(TurtleSegment::Single(DrawElement::Move(
|
||||
MoveCommand::Circle { radius, angle },
|
||||
)));
|
||||
self
|
||||
}
|
||||
|
||||
fn circle_right<IntoAngle, IntoDistance: Neg + Neg<Output = IntoDistance>>(
|
||||
&mut self,
|
||||
radius: IntoDistance,
|
||||
extend: IntoAngle,
|
||||
) -> &mut Self
|
||||
where
|
||||
Angle<Precision>: From<IntoAngle>,
|
||||
Length: From<IntoDistance>,
|
||||
{
|
||||
self.circle(-radius, extend);
|
||||
println!("Warning: circle with right arc not working yet...");
|
||||
self
|
||||
}
|
||||
}
|
||||
186
turtle-lib/src/commands.rs
Normal file
186
turtle-lib/src/commands.rs
Normal file
@ -0,0 +1,186 @@
|
||||
use bevy::{
|
||||
prelude::Component,
|
||||
reflect::{FromReflect, Reflect},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
builders::WithCommands,
|
||||
drawing::{
|
||||
animation::{
|
||||
draw_circle_segment, draw_straight_segment, move_straight_segment, turtle_turn,
|
||||
ToAnimationSegment, TurtleAnimationSegment,
|
||||
},
|
||||
TurtleGraphElement,
|
||||
},
|
||||
general::{angle::Angle, length::Length, Coordinate, Precision, Speed},
|
||||
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, Reflect, FromReflect, Debug, Clone)]
|
||||
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, Reflect, FromReflect, Default, Debug, Clone)]
|
||||
pub enum Breadcrumb {
|
||||
Dot,
|
||||
#[default]
|
||||
Stamp,
|
||||
}
|
||||
|
||||
/// Different ways that change the orientation of the turtle.
|
||||
#[derive(Component, Reflect, FromReflect, Debug, Clone)]
|
||||
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, Reflect, FromReflect, Debug, Clone)]
|
||||
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 } => {
|
||||
draw_circle_segment(state, *radius, *angle)
|
||||
}
|
||||
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(e) => match e {
|
||||
OrientationCommand::Left(angle_to_turn) => turtle_turn(state, -*angle_to_turn),
|
||||
OrientationCommand::Right(angle_to_turn) => turtle_turn(state, *angle_to_turn),
|
||||
OrientationCommand::SetHeading => todo!(),
|
||||
OrientationCommand::LookAt(_) => todo!(),
|
||||
},
|
||||
DrawElement::Drip(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, FromReflect, Debug, Clone)]
|
||||
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, Reflect, Debug)]
|
||||
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)
|
||||
}
|
||||
pub fn extend(&mut self, segments: Vec<TurtleSegment>) {
|
||||
self.commands.extend(segments.into_iter())
|
||||
}
|
||||
pub fn set_speed(&mut self, speed: Speed) {
|
||||
self.state.set_speed(speed);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WithCommands for TurtleCommands {
|
||||
fn get_mut_commands(&mut self) -> &mut Vec<TurtleSegment> {
|
||||
&mut self.commands
|
||||
}
|
||||
|
||||
fn get_commands(self) -> Vec<TurtleSegment> {
|
||||
self.commands
|
||||
}
|
||||
}
|
||||
12
turtle-lib/src/debug.rs
Normal file
12
turtle-lib/src/debug.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use bevy::prelude::Plugin;
|
||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||
|
||||
pub struct DebugPlugin;
|
||||
|
||||
impl Plugin for DebugPlugin {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
if cfg!(debug_assertions) {
|
||||
app.add_plugin(WorldInspectorPlugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
turtle-lib/src/drawing.rs
Normal file
15
turtle-lib/src/drawing.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use bevy::reflect::{FromReflect, Reflect};
|
||||
|
||||
pub use self::line_segments::{TurtleDrawCircle, TurtleDrawLine};
|
||||
|
||||
pub mod animation;
|
||||
mod line_segments;
|
||||
pub(crate) mod run_step;
|
||||
|
||||
#[derive(Reflect, FromReflect, Default, Debug)]
|
||||
pub enum TurtleGraphElement {
|
||||
TurtleLine(TurtleDrawLine),
|
||||
TurtleCircle(TurtleDrawCircle),
|
||||
#[default]
|
||||
Noop,
|
||||
}
|
||||
242
turtle-lib/src/drawing/animation.rs
Normal file
242
turtle-lib/src/drawing/animation.rs
Normal file
@ -0,0 +1,242 @@
|
||||
mod circle_lens;
|
||||
mod line_lens;
|
||||
|
||||
use bevy::prelude::{Quat, Transform, Vec2, Vec3};
|
||||
use bevy_prototype_lyon::prelude::Path;
|
||||
use bevy_tweening::{
|
||||
lens::{TransformPositionLens, TransformRotateZLens},
|
||||
Animator, EaseFunction, Tween,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
general::{angle::Angle, length::Length, 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)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_circle_segment(
|
||||
state: &mut TurtleState,
|
||||
radius: Length,
|
||||
angle: Angle<Precision>,
|
||||
) -> TurtleAnimationSegment {
|
||||
let animation = MoveCircleTurtleAnimation::new(state, radius, angle);
|
||||
let line_animation = MoveCircleLineAnimation::new(state, radius, angle);
|
||||
state.set_position(animation.end);
|
||||
state.set_heading(animation.end_heading);
|
||||
TurtleAnimationSegment {
|
||||
turtle_animation: Some(animation.animation),
|
||||
line_segment: Some(TurtleGraphElement::TurtleCircle(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MoveCircleLineAnimation {
|
||||
_start: Coordinate,
|
||||
_end: Coordinate,
|
||||
line: TurtleDrawCircle,
|
||||
animation: Tween<Path>,
|
||||
}
|
||||
|
||||
impl MoveCircleLineAnimation {
|
||||
fn new(state: &TurtleState, radius: Length, angle: Angle<Precision>) -> Self {
|
||||
let radii = Vec2::ONE * radius.0.abs();
|
||||
let start = state.position();
|
||||
let left_right = Angle::degrees(if radius.0 >= 0. { 90. } else { -90. });
|
||||
let center = state.position()
|
||||
+ (Vec2::new(radius.0.abs(), 0.).rotate(Vec2::from_angle(
|
||||
((state.heading() + left_right).to_radians()).value(),
|
||||
)));
|
||||
let end_pos = center
|
||||
+ Vec2::new(radius.0.abs(), 0.).rotate(Vec2::from_angle(
|
||||
(state.heading() + angle - left_right).to_radians().value(),
|
||||
));
|
||||
|
||||
let line =
|
||||
TurtleDrawCircle::new(center, radii, Angle::degrees(0.), state.position(), end_pos);
|
||||
let line_animator = Tween::new(
|
||||
EaseFunction::QuadraticInOut,
|
||||
state.animation_duration(),
|
||||
CircleAnimationLens {
|
||||
start_pos: state.position(),
|
||||
center,
|
||||
radii,
|
||||
start: Angle::degrees(0.),
|
||||
end: if radius.0 > 0. { angle } else { -angle },
|
||||
},
|
||||
);
|
||||
Self {
|
||||
_start: start,
|
||||
_end: end_pos,
|
||||
line,
|
||||
animation: line_animator,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MoveCircleTurtleAnimation {
|
||||
_start: Coordinate,
|
||||
end: Coordinate,
|
||||
end_heading: Angle<Precision>,
|
||||
animation: Tween<Transform>,
|
||||
}
|
||||
|
||||
impl MoveCircleTurtleAnimation {
|
||||
fn new(state: &TurtleState, radius: Length, angle: Angle<Precision>) -> Self {
|
||||
let start = state.position();
|
||||
let left_right = Angle::degrees(if radius.0 >= 0. { 90. } else { -90. });
|
||||
let center = state.position()
|
||||
+ (Vec2::new(radius.0.abs(), 0.).rotate(Vec2::from_angle(
|
||||
((state.heading() + left_right).to_radians()).value(),
|
||||
)));
|
||||
let end_heading = state.heading() + if radius.0 > 0. { angle } else { -angle };
|
||||
let end_pos = center
|
||||
+ Vec2::new(radius.0.abs(), 0.).rotate(Vec2::from_angle(
|
||||
(state.heading() + angle - 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: if radius.0 > 0. { angle } else { -angle },
|
||||
center,
|
||||
},
|
||||
)
|
||||
.with_completed_event(state.segment_index() as u64);
|
||||
Self {
|
||||
_start: start,
|
||||
end: end_pos,
|
||||
end_heading,
|
||||
animation: turtle_movement_animation,
|
||||
}
|
||||
}
|
||||
}
|
||||
51
turtle-lib/src/drawing/animation/circle_lens.rs
Normal file
51
turtle-lib/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
turtle-lib/src/drawing/animation/line_lens.rs
Normal file
24
turtle-lib/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);
|
||||
}
|
||||
}
|
||||
102
turtle-lib/src/drawing/line_segments.rs
Normal file
102
turtle-lib/src/drawing/line_segments.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use bevy::{
|
||||
prelude::{Bundle, Color, Component, Name, Transform, Vec2},
|
||||
reflect::{FromReflect, Reflect},
|
||||
};
|
||||
use bevy_prototype_lyon::{
|
||||
entity::ShapeBundle,
|
||||
prelude::{DrawMode, FillMode, GeometryBuilder, PathBuilder, StrokeMode},
|
||||
shapes::Line,
|
||||
};
|
||||
|
||||
use crate::general::{angle::Angle, Precision};
|
||||
|
||||
#[derive(Bundle, Reflect, FromReflect, Default)]
|
||||
pub struct TurtleDrawLine {
|
||||
#[reflect(ignore)]
|
||||
line: ShapeBundle,
|
||||
name: Name,
|
||||
marker: LineMarker,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TurtleDrawLine {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TurtleDrawLine")
|
||||
.field("name", &self.name)
|
||||
.field("marker", &self.marker)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default, Reflect, FromReflect, Debug, Clone, Copy)]
|
||||
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, Reflect, FromReflect, Default)]
|
||||
|
||||
pub struct TurtleDrawCircle {
|
||||
#[reflect(ignore)]
|
||||
line: ShapeBundle,
|
||||
name: Name,
|
||||
marker: CircleMarker,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TurtleDrawCircle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TurtleDrawCircle")
|
||||
.field("name", &self.name)
|
||||
.field("marker", &self.marker)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default, Reflect, FromReflect, Debug, Clone)]
|
||||
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
turtle-lib/src/drawing/run_step.rs
Normal file
58
turtle-lib/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
turtle-lib/src/events.rs
Normal file
3
turtle-lib/src/events.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use bevy::prelude::Entity;
|
||||
|
||||
pub struct DrawingStartedEvent(pub Entity);
|
||||
9
turtle-lib/src/general.rs
Normal file
9
turtle-lib/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;
|
||||
236
turtle-lib/src/general/angle.rs
Normal file
236
turtle-lib/src/general/angle.rs
Normal file
@ -0,0 +1,236 @@
|
||||
use std::{
|
||||
f32::consts::PI,
|
||||
ops::{Add, Div, Mul, Neg, Rem, Sub},
|
||||
};
|
||||
|
||||
use bevy::reflect::{FromReflect, Reflect};
|
||||
|
||||
use super::Precision;
|
||||
|
||||
#[derive(Reflect, FromReflect, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AngleUnit<T: Default + Send + Sync + Reflect + Copy + FromReflect> {
|
||||
Degrees(T),
|
||||
Radians(T),
|
||||
}
|
||||
|
||||
impl<T: Default + Send + Sync + Reflect + Copy + FromReflect> Default for AngleUnit<T> {
|
||||
fn default() -> Self {
|
||||
Self::Degrees(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Reflect, FromReflect, Copy, Default, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Angle<T: Default + Send + Sync + Reflect + Copy + FromReflect> {
|
||||
value: AngleUnit<T>,
|
||||
}
|
||||
|
||||
impl<T: From<i16> + Default + Send + Sync + Reflect + Copy + FromReflect> From<i16> for Angle<T> {
|
||||
fn from(i: i16) -> Self {
|
||||
Self {
|
||||
value: AngleUnit::Degrees(T::from(i)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Send + Sync + Reflect + Clone + Copy + FromReflect + 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 + Send + Sync + Reflect + Copy + FromReflect + 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 + Send + Sync + Reflect + Copy + FromReflect + 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 + Send + Sync + Reflect + Copy + FromReflect + 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 + Send + Sync + Reflect + Copy + FromReflect + 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 + Send + Sync + Reflect + Copy + FromReflect> 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 + Send + Sync + Reflect + Copy + FromReflect + 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>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Reflect
|
||||
+ Copy
|
||||
+ FromReflect
|
||||
+ 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
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Reflect
|
||||
+ Copy
|
||||
+ FromReflect
|
||||
+ 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);
|
||||
}
|
||||
19
turtle-lib/src/general/length.rs
Normal file
19
turtle-lib/src/general/length.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use bevy::reflect::{FromReflect, Reflect};
|
||||
|
||||
use super::Precision;
|
||||
|
||||
#[derive(Reflect, FromReflect, Default, Copy, Clone, Debug)]
|
||||
pub struct Length(pub Precision);
|
||||
|
||||
impl From<i16> for Length {
|
||||
fn from(i: i16) -> Self {
|
||||
Self(Precision::from(i))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Length {
|
||||
fn from(i: f32) -> Self {
|
||||
#[allow(clippy::useless_conversion)]
|
||||
Self(Precision::from(i))
|
||||
}
|
||||
}
|
||||
118
turtle-lib/src/lib.rs
Normal file
118
turtle-lib/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_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;
|
||||
|
||||
pub mod builders;
|
||||
mod commands;
|
||||
mod debug;
|
||||
mod drawing;
|
||||
pub mod events;
|
||||
mod general;
|
||||
pub mod shapes;
|
||||
mod state;
|
||||
pub 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_type::<TurtleColors>()
|
||||
.register_type::<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")
|
||||
}
|
||||
}
|
||||
}
|
||||
15
turtle-lib/src/shapes.rs
Normal file
15
turtle-lib/src/shapes.rs
Normal file
@ -0,0 +1,15 @@
|
||||
mod turtle;
|
||||
use bevy::{
|
||||
prelude::{Color, Component},
|
||||
reflect::Reflect,
|
||||
};
|
||||
pub use turtle::turtle;
|
||||
|
||||
#[derive(Clone, Component, Reflect)]
|
||||
pub struct TurtleShape;
|
||||
|
||||
#[derive(Clone, Component, Reflect, Default, Debug)]
|
||||
pub struct TurtleColors {
|
||||
color: Color,
|
||||
fill_color: Color,
|
||||
}
|
||||
46
turtle-lib/src/shapes/turtle.rs
Normal file
46
turtle-lib/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()
|
||||
}
|
||||
71
turtle-lib/src/state.rs
Normal file
71
turtle-lib/src/state.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use std::{cmp::max, time::Duration};
|
||||
|
||||
use bevy::{
|
||||
prelude::{Component, Transform},
|
||||
reflect::Reflect,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
commands::TurtleSegment,
|
||||
general::{angle::Angle, Coordinate, Precision, Speed, Visibility},
|
||||
shapes::TurtleColors,
|
||||
};
|
||||
|
||||
/// Describing the full state of a turtle.
|
||||
#[derive(Component, Reflect, Default, Debug, Clone)]
|
||||
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
|
||||
}
|
||||
}
|
||||
103
turtle-lib/src/turtle_bundle.rs
Normal file
103
turtle-lib/src/turtle_bundle.rs
Normal file
@ -0,0 +1,103 @@
|
||||
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::{
|
||||
builders::{
|
||||
CurvedMovement, DirectionalMovement, InvisibleLinesPlan, StopLine, Turnable, TurtlePlan,
|
||||
WithCommands,
|
||||
},
|
||||
commands::{TurtleCommands, TurtleSegment},
|
||||
general::Speed,
|
||||
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 apply_plan(&mut self, plan: TurtlePlan) {
|
||||
self.commands = TurtleCommands::new(plan.get_commands());
|
||||
}
|
||||
pub fn extend_plan(&mut self, plan: TurtlePlan) {
|
||||
self.commands.extend(plan.get_commands())
|
||||
}
|
||||
pub fn create_plan(&self) -> TurtlePlan {
|
||||
TurtlePlan::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TurtleBundle {
|
||||
pub fn set_speed(&mut self, speed: Speed) {
|
||||
self.commands.set_speed(speed);
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
impl WithCommands for TurtleBundle {
|
||||
fn get_mut_commands(&mut self) -> &mut Vec<TurtleSegment> {
|
||||
self.commands.get_mut_commands()
|
||||
}
|
||||
|
||||
fn get_commands(self) -> Vec<TurtleSegment> {
|
||||
self.commands.get_commands()
|
||||
}
|
||||
}
|
||||
impl StopLine<TurtleBundle> for TurtleBundle {
|
||||
fn pen_up(self) -> crate::builders::InvisibleLinesPlan<TurtleBundle> {
|
||||
{
|
||||
InvisibleLinesPlan::new(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectionalMovement for TurtleBundle {}
|
||||
impl Turnable for TurtleBundle {}
|
||||
impl CurvedMovement for TurtleBundle {}
|
||||
Loading…
x
Reference in New Issue
Block a user