Types/Turtle/Circle.ps1

<#
.SYNOPSIS
    Draws a circle.
.DESCRIPTION
    Draws a whole or partial circle using turtle graphics.

    That is, it draws a circle by moving the turtle forward and rotating it.

    To draw a semicircle, use an extent of 0.5.

    To draw a quarter circle, use an extent of 0.25.

    To draw a half hexagon, use an extent of 0.5 and step count of 6.
.EXAMPLE
    turtle circle 42 | Save-Turtle ./Circle.svg
.EXAMPLE
    turtle circle 42 .5 | Save-Turtle ./HalfCircle.svg
.EXAMPLE
    turtle circle 42 .25 | Save-Turtle ./QuarterCircle.svg
.EXAMPLE
    turtle circle 42 | Save-Turtle ./CirclePattern.svg
.EXAMPLE
    turtle start 21 21 circle 42 morph @(
        turtle start 21 21 circle 42
        turtle start 21 21 circle -42
        turtle start 21 21 circle 42
    ) | Save-Turtle ./CircleMorphPattern.svg
.EXAMPLE
    $turtle = New-Turtle
    $turtle.Circle(10).Pattern.Save("$pwd/CirclePattern.svg")
.EXAMPLE
    turtle circle 42 1 90 morph |
        Save-Turtle ./CircleConstructionMorph.svg
.EXAMPLE
    turtle forward 42 rotate -90 Circle 21 Circle 21 .5 rotate -90 forward 42 |
        Save-Turtle ./DashDotDash.svg
.EXAMPLE
    turtle @('forward', 40, 'Circle', 10, .75 * 4) |
        Save-Turtle ./CommandSymbol.svg
.EXAMPLE
    turtle @('forward', 40, 'Circle', 10, .75 * 4) morph |
        Save-Turtle ./CommandSymbolStepMorph.svg
.EXAMPLE
    turtle @('forward', 40, 'Circle', 10, .75 * 4) |
        Save-Turtle ./CommandSymbolPattern.svg
#>

param(
# The radius of the circle
[double]$Radius = 42,
# The portion of the circle to draw.
[double]$Extent = 1,
# The number of steps.
# If this is not provided, steps will be automatically determined
# If the the extent is between `1` or `-1` and the angle is a multiple of 90,
# then the circle will be drawn in up to four steps.
# Otherwise, the step count will default to 180.
[int]$StepCount
)

if (-not $this) { return }
if ($extent -eq 0) { return $this }

# If the step count was not specified, and the `-Extent` is `1` or `-1`,
# we want to draw an optimized path around the circle.
if ((-not $StepCount) -and (
        -not (($extent * 360) % 90)
    ) -and 
    $extent -le 1 -and 
    $extent -ge -1
) {
    # First, we need to know what the center is.
    # Luckily, the center is always a right triangle away
    $headingToCenter = $this.Heading + 90
    $circleCenter = [Numerics.Vector2]::new(
        $this.X + ($radius * [math]::cos($headingToCenter * [Math]::PI / 180)),
        $this.Y + ($radius * [math]::sin($headingToCenter * [Math]::PI / 180))
    )

    # Once we know the center, we can construct four vectors for each quadrant of the circle
    $circleRight, $circleBottom, $circleLeft, $circleTop = foreach ($n in 0..3) {
        $headingTo = $this.Heading + (90 * $n)
        $circleCenter + [Numerics.Vector2]::new(
            $radius * [math]::cos($headingTo * [Math]::PI / 180),
            $radius * [math]::sin($headingTo * [Math]::PI / 180)
        )
    }
    # and then we can draw up to four relative arcs.
    # (this ensures a pure circle is smoothly drawn and the bounding box is updated accordingly)
    $updated = switch ($extent * 360) {
        90 {
            $this.
                Arc($radius, $radius, 0, $false, $true,$circleRight.X - $this.X, $circleRight.Y - $this.Y).
                Rotate(90)
        }
        -90 {
            $this.
                Arc($radius, $radius, 0, $false, $false,$circleLeft.X - $this.X, $circleLeft.Y - $this.Y).
                Rotate(-90)
        }
        180 {
            $this.
                Arc($radius, $radius, 0, $false, $true,$circleRight.X - $this.X, $circleRight.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $true,$circleBottom.X - $this.X, $circleBottom.Y - $this.Y).
                Rotate(180)
        }
        -180  {
            $this.
                Arc($radius, $radius, 0, $false, $false,$circleLeft.X - $this.X, $circleLeft.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $false,$circleBottom.X - $this.X, $circleBottom.Y - $this.Y).
                Rotate(-180)
        }
        270 {
            $this.
                Arc($radius, $radius, 0, $false, $true,$circleRight.X - $this.X, $circleRight.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $true,$circleBottom.X - $this.X, $circleBottom.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $true,$circleLeft.X - $this.X, $circleLeft.Y - $this.Y).
                Rotate(270)
        }
        -270 {
            $this.
                Arc($radius, $radius, 0, $false, $false,$circleLeft.X - $this.X, $circleLeft.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $false,$circleBottom.X - $this.X, $circleBottom.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $false,$circleRight.X - $this.X, $circleRight.Y - $this.Y).
                Rotate(-270)
        }
        360 {
            $this.
                Arc($radius, $radius, 0, $false, $true,$circleRight.X - $this.X, $circleRight.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $true,$circleBottom.X - $this.X, $circleBottom.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $true,$circleLeft.X - $this.X, $circleLeft.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $true,$circleTop.X - $this.X, $circleTop.Y - $this.Y)
        }
        -360 {
            $this.
                Arc($radius, $radius, 0, $false, $false,$circleLeft.X - $this.X, $circleLeft.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $false,$circleBottom.X - $this.X, $circleBottom.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $false,$circleRight.X - $this.X, $circleRight.Y - $this.Y).
                Arc($radius, $radius, 0, $false, $false,$circleTop.X - $this.X, $circleTop.Y - $this.Y)
        }
    }

    # If we drew our arcs
    if ($updated) {
        # return the updated turtle
        return $updated
    }
}

# If no step count was specified, default to 180
if (-not $StepCount) { $StepCount = 180 }

# Determine the circumference of the circle
$circumference = 2 * [math]::PI * $Radius
# and divide by the number of steps
$circumferenceStep = $circumference / $StepCount

# Get a multiplier for our extent, so we turn in the right direction
$extentMultiplier = if ($extent -gt 0) { 1 } else { -1 }

$currentExtent = 0
$maxExtent = [math]::Abs($extent)
# determine how much extent each step covers.
$extentStep = 1/$StepCount

# Every step we take
$null = foreach ($n in 1..$StepCount) {
    # we move forward by a portion of the circumference
    $this.Forward($circumferenceStep)
    $currentExtent += $extentStep

    # and we rotate (as long as we would not exceed the extent).
    if ($n -le $StepCount -and $currentExtent -le $maxExtent) {
        $this.Rotate( (360 / $StepCount) * $extentMultiplier)
    }

    if ($currentExtent -gt $maxExtent) {
        break
    }
}
# Once we have taken all of the necessary steps, return this so we never break the chain.
return $this