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

Fixed turn_towards #40

Merged
merged 4 commits into from
Dec 10, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions src/turtle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -989,9 +989,6 @@ impl Turtle {
///
/// If the coordinates are the same as the turtle's current position, no rotation takes place.
/// Always rotates the least amount necessary in order to face the given point.
///
/// ## UNSTABLE
/// This feature is currently unstable and completely buggy. Do not use it until it is fixed.
pub fn turn_towards(&mut self, target: Point) {
let target_x = target[0];
let target_y = target[1];
Expand All @@ -1000,19 +997,24 @@ impl Turtle {
let x = position[0];
let y = position[1];

// If the target is (approximately) on the turtle don't turn
if (target_x - x).abs() < 0.1 && (target_y - y).abs() < 0.1 {
return;
}

let heading = self.window.fetch_turtle().heading;

// Calculate the target angle to reach
let angle = (target_y - y).atan2(target_x - x);
let angle = Radians::from_radians_value(angle);
// Calculate how much turning will be needed (angle - heading)
// And clamp it make sure the turtle doesn't turn more than 360 degrees
let angle = (angle - heading) % radians::TWO_PI;
// Try to rotate as little as possible
let angle = if angle.abs() > radians::PI {
// Using signum to deal with negative angles properly
angle.signum()*(radians::TWO_PI - angle.abs())
// Use signum to make sure the angle has the right sign
// And the turtle turns the right way
-angle.signum()*(radians::TWO_PI - angle.abs())
}
else {
angle
Expand Down Expand Up @@ -1156,4 +1158,22 @@ mod tests {
assert_eq!(turtle.position()[1].round(), 100.0);
assert_eq!(turtle.heading(), 51.0);
}

#[test]
fn turn_towards() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating this! I think you need to move lines 1168-1170 into the inner loop. Otherwise you are rotating to original_angle only once, then essentially rotating in a circle without actually going back to the original angle. The goal is to test that you can rotate from any angle to any angle. That means we need to go back to the original angle every time in order to make an exhaustive test.

If you make that change, you'll find that the test actually doesn't pass anymore. In fact, I made a little demo application which reveals that a bug still remains in the implementation of turn_towards.

Try running this by overwriting the contents of an example like examples/circle.rs and then running it with cargo run --example circle (don't commit that change, this is just to help you debug) Click to advance to each rotation. This should be a really helpful tool for you to debug what is going on. Feel free to modify it as you see fit in order to help you figure out the problem.

extern crate turtle;
use turtle::Turtle;

use std::f64::consts::PI;

fn main() {
    let mut turtle = Turtle::new();

    // Turn from each cardinal direction to each cardinal direction
    for n in 0..16 as u32 {
        let original_angle = 2.*PI * n as f64 / 16.0;
        for i in 0..16 as u32 {
            println!("{:?}", (n, i));
            turtle.wait_for_click();

            turtle.turn_towards([original_angle.cos(), original_angle.sin()]);
            //assert_eq!(turtle.heading().ceil(), original_angle.to_degrees().ceil());
            turtle.wait(1.0);

            let target_angle = 2.*PI * i as f64 / 16.0;
            turtle.turn_towards([target_angle.cos(), target_angle.sin()]);
            //assert_eq!(turtle.heading().ceil(), target_angle.to_degrees().ceil());
            turtle.forward(100.0);
            turtle.backward(100.0);
        }
        turtle.wait_for_click();
        turtle.clear();
    }
}

peek 2017-12-10 12-36

Notice that this doesn't produce a full circle of 16 lines. There are "gaps" which are the result of a bug in the calculation that still needs to be fixed. Good thing we're writing these tests! If you hadn't volunteered to do that, we wouldn't have found this problem. Thank you so much!

let mut turtle = Turtle::new();

// Turn from each cardinal direction to each cardinal direction
for n in 0..16 as u32 {
let original_angle = radians::TWO_PI * n as f64 / 16.0;
for i in 0..16 as u32 {
turtle.turn_towards([original_angle.cos(), original_angle.sin()]);
assert_eq!(turtle.heading().ceil(), original_angle.to_degrees().ceil());

let target_angle = radians::TWO_PI * i as f64 / 16.0;
turtle.turn_towards([target_angle.cos(), target_angle.sin()]);
assert_eq!(turtle.heading().ceil(), target_angle.to_degrees().ceil());
}
}
}
}