Note: This issue has been rewritten to reflect the current details as of Oct 5, 2020. See the edit history for the prior (now outdated) versions.
Goal: Provide some utility for drawing arcs. This is currently approximated in many turtle programs by many short line segments with small angles between them. For example:
|
fn arc(turtle: &mut Turtle, radius: f64, extent: f64, steps: u32) { |
|
let circumference = 2.0 * PI * radius; |
|
let distance = circumference * extent / 360.0; |
|
let step = distance / steps as f64; |
|
let rotation = extent / steps as f64; |
|
|
|
for _ in 0..steps { |
|
turtle.forward(step); |
|
turtle.right(rotation); |
|
} |
|
} |
The circle function in the Python implementation is an example of a potential API for this that works well in Python. Unfortunately, this is a bit tricky to copy directly into turtle because Rust doesn't have optional function arguments. Even if it did, I think we can do better in terms of API design in several ways:
- The Python function is called
circle which makes sense in Python because the call circle(radius) reads very naturally with all the optional arguments omitted
- In Rust, we don't have optional arguments, so we'd probably want to call it
arc so it reads naturally when both radius and extent are provided, e.g. arc(radius, extent)
- The Python implementation takes a
steps parameter because it approximates the curve with straight lines
- The Python implementation deviates from the rest of the turtle API by forcing the user to use a positive/negative radius convention to choose the direction of the arc
- We have the opportunity to add
arc_left and arc_right methods instead that are consistent with the left and right methods
- Note that both
left and right support passing in negative values so arc_left and arc_right would as well
- The Python implementation is the way it is because of the
ARC command in the LOGO programming language (We are not bound by the design decisions of LOGO or Python, so it makes sense to try to do better here)
Aside: The Builder Pattern
Usually, when an API needs optional arguments in Rust, we turn to the builder pattern. (We could use Option to simulate optional arguments, but that is unidiomatic and difficult to read.) For example, we could have an Arc struct with an API that looks like:
turtle.arc(Arc::new(5.0) // arc with radius 5.0
.extent(45.0)); // extent of 45 degrees (optional, default = 360.0)
This definitely makes sense when the struct being initialized has lots of options and reasonable defaults. In this case we only have two arguments (maybe 3 if you include Python's steps). A default of 360° for extent only makes sense if the method is called circle. We could rename the method and the builder, but turtle.circle(Circle::new(5.0)) doesn't really improve anything and is far too verbose.
Mentoring Instructions
To make this issue easier to implement and easier to review, I've split it into two parts: 1) adding arc_left and arc_right methods that we simulate using forward and right and 2) implementing the full circular arc animation system. That way we can get the method implemented and documented without needing you to master the math required for the animation right away.
Part 1: Adding the methods
Read through all the instructions first before getting started.
Part 2: Arc Animation
TODO: Please ask for these mentoring instructions when Part 1 has been completed.
Notes:
As always, please don't hesitate to questions if anything is unclear or needs more explanation!
Goal: Provide some utility for drawing arcs. This is currently approximated in many turtle programs by many short line segments with small angles between them. For example:
turtle/examples/snowman.rs
Lines 191 to 201 in cc40975
The
circlefunction in the Python implementation is an example of a potential API for this that works well in Python. Unfortunately, this is a bit tricky to copy directly into turtle because Rust doesn't have optional function arguments. Even if it did, I think we can do better in terms of API design in several ways:circlewhich makes sense in Python because the callcircle(radius)reads very naturally with all the optional arguments omittedarcso it reads naturally when bothradiusandextentare provided, e.g.arc(radius, extent)stepsparameter because it approximates the curve with straight linesarcmethod onpathfinder_canvasarc_leftandarc_rightmethods instead that are consistent with theleftandrightmethodsleftandrightsupport passing in negative values soarc_leftandarc_rightwould as wellARCcommand in the LOGO programming language (We are not bound by the design decisions of LOGO or Python, so it makes sense to try to do better here)Aside: The Builder Pattern
Usually, when an API needs optional arguments in Rust, we turn to the builder pattern. (We could use
Optionto simulate optional arguments, but that is unidiomatic and difficult to read.) For example, we could have anArcstruct with an API that looks like:This definitely makes sense when the struct being initialized has lots of options and reasonable defaults. In this case we only have two arguments (maybe 3 if you include Python's
steps). A default of 360° forextentonly makes sense if the method is calledcircle. We could rename the method and the builder, butturtle.circle(Circle::new(5.0))doesn't really improve anything and is far too verbose.Mentoring Instructions
To make this issue easier to implement and easier to review, I've split it into two parts: 1) adding
arc_leftandarc_rightmethods that we simulate usingforwardandrightand 2) implementing the full circular arc animation system. That way we can get the method implemented and documented without needing you to master the math required for the animation right away.Part 1: Adding the methods
Read through all the instructions first before getting started.
arc_left(radius: Distance, extent: Angle)method andarc_right(radius: f64, extent: f64)method toTurtlearc_leftmethod turns the turtle left (counterclockwise) as it draws and thearc_rightmethod turns the turtle right (clockwise) as it drawsradiusargument causes the center of the arc to extend out to the left of the turtle inarc_leftand to the right of the turtle inarc_rightradiusargument is negative, the center of the arc flips to the opposite side of the turtleextentargument dictates how much of a circle to drawextentargument is negative, the turtle moves backwards instead of forwardsextentcan be more than 360 degrees (in that case, the turtle would draw a complete circle and then keep drawing around the same circle until it reaches the desired angle)Turtlecalledarc_leftandarc_rightthat block on corresponding methods onAsyncTurtle(add them underneath thewaitmethod)turtle/src/turtle.rs
Lines 191 to 193 in cc40975
arc_leftandarc_rightasync methods onAsyncTurtlethatawaiton a newcircular_arcmethod onProtocolClient(add them underneath thewaitmethod)turtle/src/async_turtle.rs
Lines 88 to 91 in cc40975
ProtocolClientwith signaturecircular_arc(radius: Distance, extent: Radians, direction: RotationDirection)(add the method underneathrotate_in_place)Radianstype to disambiguate the unit of the angle andRotationDirectionto disambiguate between calls toarc_leftandarc_rightmove_forwardandrotate_in_placemethods onProtocolClientRadiansas neededTurtle::arc_leftandTurtle::arc_right)forward,backward,left, andrightfor some examples how this could be structuredcirclefunction in the Python implementation for how they described itexamples/directory that draws something cool using this featureProtocolClientto see if the extent or radius are zero/infturtle/src/ipc_protocol/protocol.rs
Lines 355 to 357 in cc40975
Turtleas unstable since we'll want people to try out the API before we decide to keep itturtle/src/drawing.rs
Lines 396 to 397 in de9fa48
CONTRIBUTING.mdPart 2: Arc Animation
TODO: Please ask for these mentoring instructions when Part 1 has been completed.
Notes:
arcinpathfinder_canvasAs always, please don't hesitate to questions if anything is unclear or needs more explanation!