Skip to content

Commit

Permalink
improve turtle docs
Browse files Browse the repository at this point in the history
  • Loading branch information
cormullion committed Aug 28, 2024
1 parent f660711 commit b8efebf
Show file tree
Hide file tree
Showing 11 changed files with 12,011 additions and 137 deletions.
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ LuxorExtLatex = ["LaTeXStrings", "MathTeXEngine"]
[compat]
Aqua = "0.8"
Base64 = "1.6"
Cairo = "1.0"
Cairo = "1.0, 1.1"
Colors = "0.11, 0.12"
DataStructures = "0.18"
Dates = "1.6"
FFMPEG = "0.4.1"
FFMPEG = "^0.4.1"
FileIO = "1"
ImageIO = "0.2, 0.3, 0.4, 0.5, 0.6"
LaTeXStrings = "1.2, 1.3"
Expand Down
48 changes: 48 additions & 0 deletions data/packageversions.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# get current versions of packages from registry
# https://github.com/JuliaLang/Pkg.jl/issues/3926

import Pkg: Pkg, API, Operations, Registry

function search(pkgname; min_version=v"0.0.0", max_version=nothing)
registries = Registry.reachable_registries()
reg_ind = findfirst(r -> r.name == "General", registries)
reg = registries[reg_ind]
pkguuids_in_registry = collect(keys(reg.pkgs))
pkgnames_in_registry = [reg.pkgs[key].name for key in pkguuids_in_registry]

pkgname_ind = findfirst(==(pkgname), pkgnames_in_registry)
pkguuid = pkguuids_in_registry[pkgname_ind]

reg_pkg = get(reg, pkguuid, nothing) # Pkg.Registry.PkgEntry

info = Registry.registry_info(reg_pkg)
reg_compat_info = Registry.compat_info(info)
versions = keys(reg_compat_info)
versions = Base.filter(v -> !Registry.isyanked(info, v), collect(versions))
versions_sorted = sort(versions, rev=true)

# filter between min_version and max_version
max_version = isnothing(max_version) ? maximum(versions_sorted; init=v"0") : max_version
filter!(v -> min_version <= v <= max_version, versions_sorted)

version_list = versions_sorted[1:min(3, length(versions_sorted))]
pkg_url = info.repo

# Prints what is shown above
print("Package '$pkgname' ")
println("$(join(version_list, ", ")) ... ")
nothing
end

for pk in ["Aqua",
#"Base64",
"Cairo","Colors","DataStructures",
#"Dates",
"FFMPEG","FileIO","ImageIO","LaTeXStrings","MathTeXEngine","PolygonAlgorithms","PrecompileTools",
#"Random",
"Rsvg",
#"Test"
]

search(pk)
end
1,784 changes: 1,784 additions & 0 deletions docs/src/assets/figures/penrose.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/src/assets/figures/turtle-splash.png
Binary file not shown.
9,983 changes: 9,983 additions & 0 deletions docs/src/assets/figures/turtle-splash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
157 changes: 106 additions & 51 deletions docs/src/tutorial/turtle.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ end
```
# Introducing turtle graphics

![turtle splash](../assets/figures/turtle-splash.png)
![turtle splash](../assets/figures/turtle-splash.svg)

Luxor includes "turtle graphics". This is a way of making drawings by steering a turtle around a drawing surface. The turtle holds (somehow) a pen, and, following your instructions, draws lines behind it as it goes.
Luxor includes "turtle graphics". This is a way of making drawings by steering an imaginary turtle around a 2D drawing surface. The turtle holds (somehow) a pen, and draws lines behind it, as it wanders around the drawing obeying your instructions.

## How to type the turtle emoji

Luxor's turtles have names. You can call your turtles anything. But if you like, you can create and name your turtle with a suitable emoji:
Luxor's turtles have names. You can call your turtles anything. If you like, you can create and name your turtle with a suitable emoji:

```julia
using Luxor
Expand All @@ -21,12 +21,14 @@ using Luxor

That's "backslash colon turtle colon tab" in VS-Code, for example. (It's Unicode character `U+1F422`.)

But you can use any simple Julia variable name if you'd prefer:
Or you can use any simple Julia variable name if you'd prefer:

```julia
raphael = Turtle()
```

Then, you create a new drawing, and give the turtle some instructions.

## A very simple turtle graphics drawing

Here's a very simple example:
Expand All @@ -35,16 +37,16 @@ Here's a very simple example:
using Luxor, Colors # hide
@drawsvg begin
background("honeydew")
🐢 = Turtle()
Forward(🐢, 100)
Turn(🐢, 90)
Forward(🐢, 100)
Turn(🐢, 90)
Forward(🐢, 100)
🐢 = Turtle() # we create the turtle
Forward(🐢, 100) # go forward by 100 'steps'
Turn(🐢, 90) # turn 90° clockwise
Forward(🐢, 100) # go forward by 100 'steps'
Turn(🐢, 90) # turn 90° clockwise
Forward(🐢, 100) # go forward by 100 'steps'
end
```

Or - with the same result, without using the drawing-creation macro:
Or - with the same result, without using the drawing-creation macro (which saves a few keystrokes):

```@example
using Luxor, Colors # hide
Expand All @@ -71,7 +73,7 @@ Here's an animated view of how the image was made (I added the turtle):

![turtle first steps](../assets/figures/turtle-steps-1.gif)

Even with just these two instructions, the turtle can draw quite interesting things:
Even with just these two instructions, you can make the turtle draw quite interesting things:

![turtle more steps](../assets/figures/turtle-steps-2.gif)

Expand All @@ -88,13 +90,15 @@ end
end
```

The smaller turn angle of 85° makes the pattern more interesting. And there's no speed restriction for this turtle.
The smaller turn angle of 85° makes the pattern more interesting.

!!! note

All the turtle instructions start with an uppercase letter. (This isn't very good Julia practice, by the way!)
All the turtle instructions in Luxor.jl start with an uppercase letter. This isn't considered good practice in Julia code; initial capitalization is supposed to be restricted to module and type names. In Luxor, this bad habit (I was young once) at least helps to distinguish between turtle graphics commands and non-turtle graphics commands.

The idea of turtle graphics is that it's easy to build up complicated pictures just by repeating very simple instructions.
The idea of turtle graphics is that it's easy to build up complicated pictures just by repeating very simple instructions such as moving along a line and turning through angles.

For example, here's a function that draws a pentagon; just five sides and five 72° turns:
Here's a function that draws a pentagon; move forward five times, turn through 72° after each move:

```@example
using Luxor, Colors # hide
Expand All @@ -119,7 +123,7 @@ end
end
```

After the first pentagon is drawn, the turtle rotates 72° - so the next pentagon doesn't overlap the previous one.
After the first pentagon is drawn by `a_pentagon`, the turtle rotates 72° - so the next pentagon doesn't overlap the previous one. We've used `HueShift()` as well, so that each pentagon is drawn in a different color, starting with redw.

## More instructions

Expand All @@ -140,7 +144,7 @@ plus a few more.

All these require a turtle name as the first argument. The full list is here: [Turtle graphics](@ref).

This next drawing is also simple, but the gradual shifting hue - thanks to `HueShift()` - is effective:
This next drawing is also simple, but the gradual shifting hue - again thanks to `HueShift()` - is effective:

```@example
using Luxor, Colors
Expand Down Expand Up @@ -168,27 +172,40 @@ end

You don't have to restrict yourself to drawing lines. With `Circle()` and `Rectangle()` you can create all kinds of images.

```@example
Here's a visualization of two ways to draw the Julia logo with a turtle.

![comparing two methods](../assets/figures/turtle-logo-comparison.gif)

The version on the left is drawn by the turtle on a journey:

```julia
using Luxor, Colors # hide

@drawsvg begin
colors = [Luxor.julia_purple, Luxor.julia_red, Luxor.julia_green]
🐢 = Turtle()
S = 50
Penup(🐢)
Turn(🐢, 30) ; Forward(🐢, S) ; Pencolor(🐢, colors[1]) ; Pendown(🐢) ; Circle(🐢, 40) ; Penup(🐢)
Turn(🐢, 150) ; Forward(🐢, 2S); Pencolor(🐢, colors[2]) ; Pendown(🐢) ; Circle(🐢, 40) ; Penup(🐢)
Turn(🐢, 120) ; Forward(🐢, 2S); Pencolor(🐢, colors[3]) ; Pendown(🐢) ; Circle(🐢, 40) ; Penup(🐢)
colors = [Luxor.julia_purple, Luxor.julia_red, Luxor.julia_green]
🐢 = Turtle()
S = 50
Penup(🐢)
Turn(🐢, 30) ; Forward(🐢, S) ;
Pencolor(🐢, colors[1]); Pendown(🐢) ;
Circle(🐢, 40) ; Penup(🐢)
Turn(🐢, 150) ; Forward(🐢, 2S) ;
Pencolor(🐢, colors[2]) ; Pendown(🐢) ;
Circle(🐢, 40) ; Penup(🐢)
Turn(🐢, 120) ; Forward(🐢, 2S) ;
Pencolor(🐢, colors[3]) ; Pendown(🐢) ;
Circle(🐢, 40) ; Penup(🐢)
end
```

This is not a very elegant approach to drawing the Julia logo. Here's a better way:
THe version on the right repeats the same action three times:

```@example
```julia
using Luxor, Colors # hide

@drawsvg begin
🐢 = Turtle()
colors = [Luxor.julia_purple, Luxor.julia_red, Luxor.julia_green]
🐢 = Turtle()
for i in 1:3
Push(🐢)
Orientation(🐢, [30, 150, 270][i])
Expand All @@ -202,19 +219,19 @@ using Luxor, Colors # hide
end
```

The `Push()` instruction tells the turtle to remember the current position and rotation on a stack. `Pop()` gets that information from the stack and then teleports the turtle back to that position - forgetting where it was and where it was heading. This way, the same task is easily repeated.
The `Push()` instruction tells the turtle to remember the current position and rotation on a *stack* and continue. `Pop()` gets information from the stack and then teleports the turtle to that position and orientation - forgetting where it was and where it was heading. This way, the same task - turn-forward-pencolor-pendown-circle - is easily repeated in different directions.

## Adding new commands

There's isn't a `Back()` instruction as you might expect - because it could behave in various different ways (is there another 180° turn afterwards?). Here's how to add your own `Back()` command:
There's isn't a `Back()` instruction as you might expect - because it's not clear how it should work. For example, is it like putting a car in reverse and reversing, when you don't change the direction you're facing, or is it like turning through 180° and then going forward? And do you turn round afterwards?. But it's easy to add your own `Back()` command that does exactly what you want it to:

```@example
using Luxor, Colors # hide
function Back(t::Turtle, n)
Turn(t, 180)
Forward(t, n)
Turn(t, 180) # now looking back towards where it just was
Turn(t, 180) # looking forward again
end
function draw_graphics(🐢::Turtle)
Expand All @@ -237,32 +254,70 @@ end
end
```

## Further credit
## More turtles

Here's the code for the abstract splash image at the top of this section:

```julia
using Luxor, Colors
Drawing(800, 300, "/tmp/turtles.png")
Drawing(800, 300, "/tmp/turtles.svg")
origin()
background("black")
t = Turtle()
Pencolor(t, "red")
for i in 1:1500
Forward(t, rand(1:10))
Turn(t, rand() * π)
rand(Bool) ? Circle(t, rand(1:3)) : Rectangle(t, rand(1:10), 5)
HueShift(t, .5)
Randomize_saturation(t)
Penup(t)
Forward(t, rand(5:15))
Pendown(t)
pt = Point(t.xpos, t.ypos)
if !isinside(pt, box(BoundingBox()))
Reposition(t, pointcrossesboundingbox(pt, BoundingBox()))
Towards(t, O)
end
end
turtles = Turtle[]
for i in 1:100
turtle = Turtle(rand(BoundingBox()))
Pencolor(turtle, HSL(30rand(), 0.8, 0.7))
Orientation(turtle, 360rand())
Penwidth(turtle, .5)
push!(turtles, turtle)
for t in turtles
HueShift(t, 5)
Forward(t, rand(5:10))
Turn(t, rand(-15:15))
rand(Bool) ? Circle(t, 2) : Rectangle(t, 2, 2)
Pen_opacity_random(t)
pt = Point(t.xpos, t.ypos)
if !isinside(pt, box(BoundingBox()))
Towards(t, Point(0, 0))
end
end
end
finish()
preview()
```

This code generates a hundred turtles in random positions and moves and turns them randomly. When a turtle reaches the edge, its orientation is changed so that it points to the center.

If you enjoy drawing with turtles, you might enjoy the [Lindenmayer.jl](https://github.com/cormullion/Lindenmayer.jl) package.

```julia
using Lindenmayer
using Luxor
using Colors

@draw begin
background("black")
setlinecap("round")
penrose = LSystem(["X" => "PM++QM----YM[-PM----XM]++t",
"Y" => "+PM--QM[---XM--YM]+t",
"P" => "-XM++YM[+++PM++QM]-t",
"Q" => "--PM++++XM[+QM++++YM]--YMt",
"M" => "F",
"F" => ""],
"[Y]++[Y]++[Y]++[Y]++[Y]")

# evaluate the LSystem
Lindenmayer.evaluate(penrose, 5)

# create a turtle
🐢 = Turtle(colorant"purple")
Penwidth(🐢, 5)
Pencolor(🐢, "cyan")

# render the LSystem's evaluation to the drawing
# forward 35, turn angle 36
Lindenmayer.render(penrose, 🐢, 35, 36)
end
```

![penrose](../assets/figures/penrose.svg)
Loading

0 comments on commit b8efebf

Please sign in to comment.