diff --git a/Cargo.lock b/Cargo.lock index 7b134d2..d24aa70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24606928a235e73cdef55a0c909719cadd72fce573e5713d58cb2952d8f5794c" +checksum = "846ffacb9d0c8b879ef9e565b59e18fb76d6a61013e5bd24ecc659864e6b1a1f" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -594,9 +594,9 @@ dependencies = [ [[package]] name = "bevy_pbr" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9a81bbd02f5e0a57899a41aec37d9cb14965e1e4d510547f3f680323d05c0f" +checksum = "176073021a4caeb8b448f24ce790fb57fde74b114f345064a8b102d2f7bed905" dependencies = [ "bevy_app", "bevy_asset", @@ -741,9 +741,9 @@ dependencies = [ [[package]] name = "bevy_sprite" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f83dfe8897d6c0d9d5ce3818d49a13e58ae2b9b9ecf4f4bb85aa31bb0678f68" +checksum = "69c419f3db09d7ac1f4d45e0874d349d5d6f47f48bc10d55cd0da36413e2331e" dependencies = [ "bevy_app", "bevy_asset", @@ -841,9 +841,9 @@ dependencies = [ [[package]] name = "bevy_ui" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac181a7b637da61fad72981ff9d2e5b899283ca7d54b2b7ea49c431121331c53" +checksum = "062ce086de1a4a470e5df48cb5c16a1dc97ab610e635cafabdef26c4a1ef5756" dependencies = [ "bevy_app", "bevy_asset", @@ -883,9 +883,9 @@ dependencies = [ [[package]] name = "bevy_window" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3bdc3a220a9bb2fad9bd30d5f44c6645725398fe1bc588fc87abf09f092696e" +checksum = "707dbbebfac72b1e63e874e7a11a345feab8c440355c0bd71e6dff26709fba9a" dependencies = [ "bevy_app", "bevy_ecs", @@ -3238,6 +3238,7 @@ dependencies = [ "bevy-inspector-egui", "bevy_prototype_lyon", "bevy_tweening", + "num-traits", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 49488b4..8ace4b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ bevy = { version = "0.8", features = ["dynamic"] } bevy-inspector-egui = "0.12.1" bevy_prototype_lyon = "0.6" bevy_tweening = "0.5.0" +num-traits = "0.2" # Enable a small amount of optimization in debug mode [profile.dev] diff --git a/src/datatypes.rs b/src/datatypes.rs new file mode 100644 index 0000000..8f932ea --- /dev/null +++ b/src/datatypes.rs @@ -0,0 +1 @@ +pub mod angle; diff --git a/src/datatypes/angle.rs b/src/datatypes/angle.rs new file mode 100644 index 0000000..6a0e7fb --- /dev/null +++ b/src/datatypes/angle.rs @@ -0,0 +1,154 @@ +use bevy_inspector_egui::Inspectable; +use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; + +#[derive(Inspectable, Copy, Clone, Debug, PartialEq, Eq)] +pub enum AngleUnit { + Degrees(T), + Radians(T), +} + +impl Default for AngleUnit { + fn default() -> Self { + Self::Degrees(Default::default()) + } +} + +#[derive(Inspectable, Copy, Default, Clone, Debug, PartialEq, Eq)] +pub struct Angle { + value: AngleUnit, +} + +impl> Rem for Angle { + 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> Mul for Angle { + 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> Div for Angle { + 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> Neg for Angle { + 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> Neg for &Angle { + type Output = Angle; + + fn neg(self) -> Self::Output { + match self.value.clone() { + AngleUnit::Degrees(v) => Self::Output::degrees(-v), + AngleUnit::Radians(v) => Self::Output::radians(-v), + } + } +} + +impl Angle { + pub fn degrees(value: T) -> Angle { + Self { + value: AngleUnit::Degrees(value), + } + } + pub fn radians(value: T) -> Angle { + Self { + value: AngleUnit::Radians(value), + } + } + pub fn value(&self) -> T { + match self.value.clone() { + AngleUnit::Degrees(v) => v, + AngleUnit::Radians(v) => v, + } + } +} + +impl Angle { + pub fn to_radians(self) -> Self { + match self.value { + AngleUnit::Degrees(v) => Self { + value: AngleUnit::Radians(v.to_radians()), + }, + AngleUnit::Radians(_) => self, + } + } +} + +impl + Default + num_traits::float::Float> Add for Angle { + type Output = Angle; + + 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 + Default + num_traits::float::Float> Sub for Angle { + type Output = Angle; + + 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) +} diff --git a/src/datatypes/length.rs b/src/datatypes/length.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs index 35405f5..40d4773 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod datatypes; mod debug; mod primitives; mod turtle; diff --git a/src/primitives.rs b/src/primitives.rs index 557d7d3..ae3a68c 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -6,7 +6,7 @@ use bevy_prototype_lyon::{ }; use bevy_tweening::Lens; -use crate::turtle::Angle; +use crate::datatypes::angle::Angle; pub(crate) struct LineAnimationLens { start: Vec2, @@ -58,8 +58,8 @@ pub(crate) struct CircleAnimationLens { pub start_pos: Vec2, pub center: Vec2, pub radii: Vec2, - pub start: Angle, - pub end: Angle, + pub start: Angle, + pub end: Angle, } impl Lens for CircleAnimationLens { @@ -70,7 +70,9 @@ impl Lens for CircleAnimationLens { path_builder.arc( self.center, self.radii, - (self.start.0 + ((self.end.0 - self.start.0) * ratio)).to_radians(), + (self.start + ((self.end - self.start) * ratio)) + .to_radians() + .value(), 0., ); let line = path_builder.build(); @@ -81,17 +83,17 @@ impl Lens for CircleAnimationLens { pub(crate) struct CircleMovementLens { pub center: Vec2, pub start: Transform, - pub end: Angle, + pub end: Angle, } impl Lens for CircleMovementLens { fn lerp(&mut self, target: &mut Transform, ratio: f32) { - let angle = self.end.0 * ratio; + let angle = self.end * ratio; let mut rotated = self.start; rotated.rotate_around( self.center.extend(0.), - Quat::from_rotation_z(angle.to_radians()), + Quat::from_rotation_z(angle.to_radians().value()), ); *target = rotated; @@ -114,7 +116,7 @@ impl TurtleDrawCircle { pub(crate) fn new( center: Vec2, radii: Vec2, - angle: Angle, + angle: Angle, index: u64, start: Vec2, end: Vec2, @@ -122,7 +124,7 @@ impl TurtleDrawCircle { let mut path_builder = PathBuilder::new(); path_builder.move_to(start); // 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(center, radii, angle.0.to_radians(), 0.); + path_builder.arc(center, radii, angle.to_radians().value(), 0.); /* println!("The radiuses: {}", radii); path_builder.move_to(Vec2::ZERO); diff --git a/src/turtle.rs b/src/turtle.rs index 6fdb9aa..733f6f8 100644 --- a/src/turtle.rs +++ b/src/turtle.rs @@ -9,6 +9,7 @@ use bevy_tweening::{ }; use crate::{ + datatypes::angle::Angle, primitives::{CircleAnimationLens, LineAnimationLens, TurtleDrawCircle, TurtleDrawLine}, turtle_shapes, }; @@ -44,19 +45,19 @@ impl Default for Turtle { TurtleCommand::Forward(Length(-100.)), TurtleCommand::Circle { radius: Length(150.), - angle: Angle(30.), + angle: Angle::degrees(30.), }, TurtleCommand::Forward(Length(100.)), TurtleCommand::Circle { radius: Length(70.), - angle: Angle(60.), + angle: Angle::degrees(60.), }, TurtleCommand::Forward(Length(100.)), - TurtleCommand::Right(Angle(90.)), + TurtleCommand::Right(Angle::degrees(70.)), //TurtleCommand::PenDown, TurtleCommand::Circle { radius: Length(30.), - angle: Angle(360. - 46.), + angle: Angle::degrees(360. - 46.), }, /* TurtleCommand::Backward(Length(100.)), TurtleCommand::Right(Angle(90.)), @@ -111,7 +112,7 @@ pub struct TurtleCommands { #[derive(Inspectable)] pub struct TurtleState { pub start: Vec2, - pub heading: f32, + pub heading: Angle, pub index: u64, pub drawing: bool, } @@ -123,7 +124,7 @@ impl TurtleCommands { lines: vec![], state: TurtleState { start: Vec2::ZERO, - heading: 0f32.to_radians(), + heading: Angle::degrees(0.), index: 0, drawing: true, }, @@ -144,11 +145,11 @@ impl TurtleCommands { TurtleCommand::Backward(Length(x)) => { crate::turtle_movement::turtle_move(&mut self.state, -*x as f32) } - TurtleCommand::Left(Angle(x)) => { - crate::turtle_movement::turtle_turn(&mut self.state, *x as f32) + TurtleCommand::Left(angle) => { + crate::turtle_movement::turtle_turn(&mut self.state, *angle) } - TurtleCommand::Right(Angle(x)) => { - crate::turtle_movement::turtle_turn(&mut self.state, -*x as f32) + TurtleCommand::Right(angle) => { + crate::turtle_movement::turtle_turn(&mut self.state, -angle) } TurtleCommand::PenUp => { self.state.drawing = false; @@ -182,7 +183,7 @@ pub enum TurtleGraphElement { end: Vec2, center: Vec2, radii: Vec2, - angle: Angle, + angle: Angle, }, #[default] Noop, @@ -199,22 +200,20 @@ pub struct Colors { #[derive(Inspectable, Default, Copy, Clone, Debug)] pub struct Length(f32); -#[derive(Inspectable, Default, Copy, Clone, Debug)] -pub struct Angle(pub f32); #[derive(Component, Inspectable, Default)] pub enum TurtleCommand { Forward(Length), Backward(Length), - Left(Angle), - Right(Angle), + Left(Angle), + Right(Angle), PenUp, PenDown, #[default] Pause, Circle { radius: Length, - angle: Angle, + angle: Angle, }, } @@ -284,7 +283,7 @@ fn draw_lines( start_pos: start, center, radii, - start: Angle(0.), + start: Angle::degrees(0.), end: angle, }, )); @@ -292,7 +291,7 @@ fn draw_lines( .spawn_bundle(TurtleDrawCircle::new( center, radii, - Angle(0.), + Angle::degrees(0.), tcmd.state.index, start, end, @@ -324,7 +323,7 @@ fn keypresses( let mut tcmd = tcmd.single_mut(); tcmd.state = TurtleState { start: Vec2::ZERO, - heading: 0., + heading: Angle::degrees(0.), index: 0, drawing: true, }; diff --git a/src/turtle_movement.rs b/src/turtle_movement.rs index 8ac54e4..bf26ec3 100644 --- a/src/turtle_movement.rs +++ b/src/turtle_movement.rs @@ -7,13 +7,14 @@ use bevy_tweening::{ }; use crate::{ + datatypes::angle::Angle, primitives::{CircleAnimationLens, CircleMovementLens}, - turtle::{Angle, TurtleGraphElement, TurtleState}, + turtle::{TurtleGraphElement, TurtleState}, }; pub fn turtle_turn( state: &mut TurtleState, - angle_to_turn: f32, + angle_to_turn: Angle, ) -> (Option>, Option) { let start = state.heading; let end = state.heading + (angle_to_turn * PI / 180.); @@ -24,7 +25,10 @@ pub fn turtle_turn( // Animation time Duration::from_millis(500), // Rotate the turtle - TransformRotateZLens { start, end }, + TransformRotateZLens { + start: start.value(), + end: end.value(), + }, ) .with_completed_event(state.index as u64); // Dont move and draw @@ -39,7 +43,7 @@ pub fn turtle_move( length: f32, ) -> (Option>, Option) { let start = state.start; - let end = state.start + (Vec2::from_angle(state.heading) * length); + let end = state.start + (Vec2::from_angle(state.heading.to_radians().value()) * length); let turtle_movement_animation = Tween::new( // accelerate and decelerate EaseFunction::QuadraticInOut, @@ -66,14 +70,15 @@ pub fn turtle_move( pub fn turtle_circle( state: &mut TurtleState, radius: f32, - angle: Angle, + angle: Angle, ) -> (Option>, Option) { let radius_tuple = Vec2::ONE * radius.abs(); - let left_right = if radius >= 0. { 90f32 } else { -90. }; - println!("Heading: {}", state.heading); + let left_right = Angle::degrees(if radius >= 0. { 90. } else { -90. }); + println!("Heading: {}", state.heading.value()); let center = state.start - + (Vec2::new(radius.abs(), 0.) - .rotate(Vec2::from_angle(state.heading + left_right.to_radians()))); + + (Vec2::new(radius.abs(), 0.).rotate(Vec2::from_angle( + (state.heading + left_right.to_radians()).value(), + ))); let turtle_movement_animation = Tween::new( // accelerate and decelerate @@ -85,7 +90,7 @@ pub fn turtle_circle( CircleMovementLens { start: Transform { translation: state.start.extend(0.), - rotation: Quat::from_rotation_z(state.heading), + rotation: Quat::from_rotation_z(state.heading.to_radians().value()), scale: Vec3::ONE, }, end: angle, @@ -105,6 +110,10 @@ pub fn turtle_circle( } else { TurtleGraphElement::Noop }; - // TODO update end_position : state.start = end; + let end_pos = center + + Vec2::new(radius.abs(), 0.).rotate(Vec2::from_angle( + (state.heading + angle).to_radians().value(), + )); + state.start = end_pos; (Some(turtle_movement_animation), Some(line)) }