Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement #82: Add an arc method #206

Merged
merged 9 commits into from
Oct 11, 2020
117 changes: 117 additions & 0 deletions examples/flower.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! Draws a simple geometric sort of flower with customizable dimensions.
PaulDance marked this conversation as resolved.
Show resolved Hide resolved
//!
//! This example makes extensive use of the turtle arc methods: [`arc_left`] and [`arc_right`].
//! They both provide the ability to draw a circular arc defined by the given `radius` and `extent`
//! parameters, while the first makes the turtle draw it to the left and the second to the right.

// To run this example, use the command: cargo run --features unstable --example flower
#[cfg(all(not(feature = "unstable")))]
compile_error!("This example relies on unstable features. Run with `--features unstable`");

use turtle::{Angle, Distance, Drawing};
PaulDance marked this conversation as resolved.
Show resolved Hide resolved

const TURTLE_SPEED: &str = "faster";
const BOTTOM_MARGIN: Distance = 25.0;

const LEAF_FILL_COLOR: &str = "green";
const LEAF_BORDER_COLOR: &str = "dark green";
const LEAF_BORDER_WIDTH: Distance = 1.0;
const LEFT_LEAF_RADIUS: Distance = 200.0;
const LEFT_LEAF_EXTENT: Angle = 45.0;
const RIGHT_LEAF_INCLINATION: Angle = 15.0;
const RIGHT_LEAF_BOTTOM_RADIUS: Distance = 250.0;
const RIGHT_LEAF_BOTTOM_EXTENT: Angle = 45.0;
const RIGHT_LEAF_TOP_RADIUS: Distance = 157.0;
const RIGHT_LEAF_TOP_EXTENT: Angle = 75.0;

const TRUNK_COLOR: &str = LEAF_BORDER_COLOR;
const TRUNK_WIDTH: Distance = 3.0;
const TRUNK_PIECE_COUNT: usize = 4;
const TRUNK_PIECE_RADIUS: Distance = 500.0;
const TRUNK_PIECE_EXTENT: Angle = 15.0;

const PETALS_COUNT: usize = 4;
const PETALS_FILL_COLOR: &str = "purple";
const PETALS_BORDER_COLOR: &str = "dark purple";
const PETALS_BORDER_WIDTH: Distance = LEAF_BORDER_WIDTH;
const PETALS_INIT_LEFT: Angle = 65.0;
const PETALS_SIDE_RADIUS: Distance = 80.0;
const PETALS_SIDE_EXTENT: Angle = 90.0;
const PETALS_SPACE_GAP: Angle = 20.0;
const PETALS_SPACE_RADIUS: Distance = 40.0;
const PETALS_SPACE_EXTENT: Angle = 30.0;

fn main() {
// Acquiring resources.
let mut drawing = Drawing::new();
let size = drawing.size();
let mut turtle = drawing.add_turtle();

// Initial positioning.
turtle.set_speed("instant");
turtle.pen_up();
turtle.go_to([
-(size.width as f64) / 6.0,
-(size.height as f64) / 2.0 + BOTTOM_MARGIN,
]);
turtle.pen_down();

// Setup.
turtle.use_degrees();
turtle.set_speed(TURTLE_SPEED);

// Body.
turtle.set_fill_color(LEAF_FILL_COLOR);
turtle.set_pen_color(LEAF_BORDER_COLOR);

for _ in 0..TRUNK_PIECE_COUNT {
// Leaves:
turtle.set_pen_size(LEAF_BORDER_WIDTH);
turtle.set_pen_color(LEAF_BORDER_COLOR);
turtle.begin_fill();

// Left leaf.
turtle.arc_left(LEFT_LEAF_RADIUS, LEFT_LEAF_EXTENT);
turtle.right(LEFT_LEAF_EXTENT);
turtle.arc_right(LEFT_LEAF_RADIUS, -LEFT_LEAF_EXTENT);
turtle.right(LEFT_LEAF_EXTENT);

// Right leaf.
turtle.right(RIGHT_LEAF_INCLINATION);
// Note that `arc_left` with a negative radius is the same as calling `arc_right`.
// This is used below for illustration purposes only. You'd probably want to use
// `arc_right` in real code.
turtle.arc_left(-RIGHT_LEAF_BOTTOM_RADIUS, RIGHT_LEAF_BOTTOM_EXTENT);
turtle.right(RIGHT_LEAF_INCLINATION);
turtle.arc_left(-RIGHT_LEAF_TOP_RADIUS, -RIGHT_LEAF_TOP_EXTENT);

// Trunk.
turtle.end_fill();
turtle.set_pen_size(TRUNK_WIDTH);
turtle.set_pen_color(TRUNK_COLOR);
turtle.arc_right(TRUNK_PIECE_RADIUS, TRUNK_PIECE_EXTENT);
}

// Petals.
turtle.set_fill_color(PETALS_FILL_COLOR);
turtle.set_pen_color(PETALS_BORDER_COLOR);
turtle.set_pen_size(PETALS_BORDER_WIDTH);
turtle.left(PETALS_INIT_LEFT);
turtle.begin_fill();
turtle.arc_right(PETALS_SIDE_RADIUS, PETALS_SIDE_EXTENT);

for _ in 0..PETALS_COUNT {
turtle.left(PETALS_SPACE_GAP);
turtle.arc_right(PETALS_SPACE_RADIUS, -PETALS_SPACE_EXTENT);
turtle.right(2.0 * PETALS_SPACE_GAP + PETALS_SPACE_EXTENT);
turtle.arc_left(PETALS_SPACE_RADIUS, PETALS_SPACE_EXTENT);
}

// Finish petals with error adjustments.
turtle.left(PETALS_SPACE_GAP);
turtle.arc_left(PETALS_SIDE_RADIUS + 1.0, 3.0 - PETALS_SIDE_EXTENT);
turtle.end_fill();

// Reveal final drawing.
turtle.hide();
}
22 changes: 22 additions & 0 deletions src/async_turtle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ impl AsyncTurtle {
time::delay_for(time::Duration::from_millis((secs * 1000.0) as u64)).await
}

pub async fn arc_left(&mut self, radius: Distance, extent: Angle) {
self.client
.circular_arc(
self.id,
radius,
self.angle_unit.to_radians(extent),
RotationDirection::Counterclockwise,
)
.await
}

pub async fn arc_right(&mut self, radius: Distance, extent: Angle) {
self.client
.circular_arc(
self.id,
radius,
self.angle_unit.to_radians(extent),
RotationDirection::Clockwise,
)
.await
}

pub fn into_sync(self) -> Turtle {
self.into()
}
Expand Down
15 changes: 15 additions & 0 deletions src/ipc_protocol/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,21 @@ impl ProtocolClient {
}
}

pub async fn circular_arc(&self, id: TurtleId, radius: Distance, extent: Radians, direction: RotationDirection) {
if !radius.is_normal() || !extent.is_normal() {
return;
}

let steps = 250; // Arbitrary value for now.
let step = radius.abs() * extent.to_radians() / steps as f64;
let rotation = radius.signum() * extent / steps as f64;

for _ in 0..steps {
self.move_forward(id, step).await;
self.rotate_in_place(id, rotation, direction).await;
}
}

pub fn begin_fill(&self, id: TurtleId) {
self.client.send(ClientRequest::BeginFill(id))
}
Expand Down
146 changes: 146 additions & 0 deletions src/turtle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,118 @@ impl Turtle {
block_on(self.turtle.wait(secs))
}

/// Draw a circular arc starting at the current position and going to the left of the turtle,
/// thus globally turning counterclockwise.
///
/// It is configured through the `radius` and `extent` arguments:
/// * `radius` places the center of the arc itself `radius` units away from the left of the
/// turtle, with respect to its current orientation. When negative, it does so to the right.
/// * `extent` controls how much of the arc is to be drawn, that is to say the `Angle` that
/// forms the circular portion of the given `radius`. When negative, the turtle moves
/// backwards instead, but it still goes to its left. It can also be greater than the
/// turtle's current angle unit domain limit, i.e. 360° when using degrees or 2π when using
/// radians: the turtle will simply continue to draw until the complete angle is reached.
PaulDance marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Example
///
/// ```rust
/// # use turtle::Turtle;
/// let mut turtle = Turtle::new();
///
/// assert_eq!(turtle.position(), [0.0, 0.0].into());
/// assert_eq!(turtle.heading(), 90.0);
///
/// // Quarter of a circle.
/// turtle.arc_left(100.0, 90.0);
/// assert!((turtle.position() - [-100.0, 100.0].into()).len() <= 0.5);
/// assert!((turtle.heading() - 180.0).abs() <= 0.1);
///
/// // Full circle.
/// turtle.reset();
/// turtle.arc_left(100.0, 360.0);
/// assert!((turtle.position() - [0.0, 0.0].into()).len() <= 0.5);
/// assert!((turtle.heading() - 90.0).abs() <= 0.1);
///
/// // Full + quarter circle.
/// turtle.reset();
/// turtle.arc_left(100.0, 360.0 + 90.0);
/// assert!((turtle.position() - [-100.0, 100.0].into()).len() <= 2.5);
/// assert!((turtle.heading() - 180.0).abs() <= 0.1);
///
/// // Negative radius: flip center to the right.
/// turtle.reset();
/// turtle.arc_left(-100.0, 90.0);
/// assert!((turtle.position() - [100.0, 100.0].into()).len() <= 0.5);
/// assert!(turtle.heading().abs().min((turtle.heading() - 360.0).abs()) <= 0.1);
///
/// // Negative extent: go backwards to the left.
/// turtle.reset();
/// turtle.arc_left(100.0, -90.0);
/// assert!((turtle.position() - [-100.0, -100.0].into()).len() <= 0.5);
/// assert!(turtle.heading().abs().min((turtle.heading() - 360.0).abs()) <= 0.1);
/// ```
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
pub fn arc_left(&mut self, radius: Distance, extent: Angle) {
block_on(self.turtle.arc_left(radius, extent))
}

/// Draw a circular arc starting at the current position and going to the right of the turtle,
/// thus globally turning clockwise.
///
/// It is configured through the `radius` and `extent` arguments:
/// * `radius` places the center of the arc itself `radius` units away from the right of the
/// turtle, with respect to its current orientation. When negative, it does so to the left.
/// * `extent` controls how much of the arc is to be drawn, that is to say the `Angle` that
/// forms the circular portion of the given `radius`. When negative, the turtle moves
/// backwards instead, but it still goes to its right. It can also be greater than the
/// turtle's current angle unit domain limit, i.e. 360° when using degrees or 2π when using
/// radians: the turtle will simply continue to draw until the complete angle is reached.
///
/// # Example
///
/// ```rust
/// # use turtle::Turtle;
/// let mut turtle = Turtle::new();
///
/// assert_eq!(turtle.position(), [0.0, 0.0].into());
/// assert_eq!(turtle.heading(), 90.0);
///
/// // Quarter of a circle.
/// turtle.arc_right(100.0, 90.0);
/// assert!((turtle.position() - [100.0, 100.0].into()).len() <= 0.5);
/// assert!(turtle.heading().abs().min((turtle.heading() - 360.0).abs()) <= 0.1);
///
/// // Full circle.
/// turtle.reset();
/// turtle.arc_right(100.0, 360.0);
/// assert!((turtle.position() - [0.0, 0.0].into()).len() <= 0.5);
/// assert!((turtle.heading() - 90.0).abs() <= 0.1);
///
/// // Full + quarter circle.
/// turtle.reset();
/// turtle.arc_right(100.0, 360.0 + 90.0);
/// assert!((turtle.position() - [100.0, 100.0].into()).len() <= 2.5);
/// assert!(turtle.heading().abs().min((turtle.heading() - 360.0).abs()) <= 0.1);
///
/// // Negative radius: flip center to the left.
/// turtle.reset();
/// turtle.arc_right(-100.0, 90.0);
/// assert!((turtle.position() - [-100.0, 100.0].into()).len() <= 0.5);
/// assert!((turtle.heading() - 180.0).abs() <= 0.1);
///
/// // Negative extent: go backwards to the right.
/// turtle.reset();
/// turtle.arc_right(100.0, -90.0);
/// assert!((turtle.position() - [100.0, -100.0].into()).len() <= 0.5);
/// assert!((turtle.heading() - 180.0).abs() <= 0.1);
/// ```
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
pub fn arc_right(&mut self, radius: Distance, extent: Angle) {
block_on(self.turtle.arc_right(radius, extent))
}

pub(crate) fn into_async(self) -> AsyncTurtle {
self.turtle
}
Expand Down Expand Up @@ -1035,6 +1147,40 @@ mod tests {
turtle.wait(::std::f64::INFINITY);
turtle.wait(-::std::f64::INFINITY);

turtle.arc_left(0.0, 0.0);
turtle.arc_left(0.0, f64::NAN);
turtle.arc_left(0.0, f64::INFINITY);
turtle.arc_left(0.0, -f64::INFINITY);
turtle.arc_left(f64::NAN, 0.0);
turtle.arc_left(f64::NAN, f64::NAN);
turtle.arc_left(f64::NAN, f64::INFINITY);
turtle.arc_left(f64::NAN, -f64::INFINITY);
turtle.arc_left(f64::INFINITY, 0.0);
turtle.arc_left(f64::INFINITY, f64::NAN);
turtle.arc_left(f64::INFINITY, f64::INFINITY);
turtle.arc_left(f64::INFINITY, -f64::INFINITY);
turtle.arc_left(-f64::INFINITY, 0.0);
turtle.arc_left(-f64::INFINITY, f64::NAN);
turtle.arc_left(-f64::INFINITY, f64::INFINITY);
turtle.arc_left(-f64::INFINITY, -f64::INFINITY);

turtle.arc_right(0.0, 0.0);
turtle.arc_right(0.0, f64::NAN);
turtle.arc_right(0.0, f64::INFINITY);
turtle.arc_right(0.0, -f64::INFINITY);
turtle.arc_right(f64::NAN, 0.0);
turtle.arc_right(f64::NAN, f64::NAN);
turtle.arc_right(f64::NAN, f64::INFINITY);
turtle.arc_right(f64::NAN, -f64::INFINITY);
turtle.arc_right(f64::INFINITY, 0.0);
turtle.arc_right(f64::INFINITY, f64::NAN);
turtle.arc_right(f64::INFINITY, f64::INFINITY);
turtle.arc_right(f64::INFINITY, -f64::INFINITY);
turtle.arc_right(-f64::INFINITY, 0.0);
turtle.arc_right(-f64::INFINITY, f64::NAN);
turtle.arc_right(-f64::INFINITY, f64::INFINITY);
turtle.arc_right(-f64::INFINITY, -f64::INFINITY);
PaulDance marked this conversation as resolved.
Show resolved Hide resolved

assert_eq!(turtle.position(), position);
assert_eq!(turtle.heading(), heading);
}
Expand Down