defmodule Vivid.Arc do
alias Vivid.{Arc, Path, Point}
import Vivid.Math
defstruct ~w(center radius start_angle range steps)a
@moduledoc ~S"""
This module represents an Arc, otherwise known as a circle segment.
## Example
iex> use Vivid
...> Arc.init(Point.init(10,10), 10, 0, 45)
...> |> to_string()
"@@@@@@\n" <>
"@@@ @\n" <>
"@@@ @@\n" <>
"@@ @@@\n" <>
"@@ @@@\n" <>
"@ @@@\n" <>
"@ @@@@\n" <>
"@ @@@@\n" <>
"@ @@@@\n" <>
"@@@@@@\n"
"""
@type t :: %Arc{center: Point.t(), radius: number, start_angle: number, steps: integer}
@doc ~S"""
Creates an Arc.
* `center` is a Point definining the center point of the arc's parent circle.
* `radius` is the radius of the parent circle.
* `start_angle` is the angle at which to start drawing the arc, `0` is the parallel to the X axis, to the left.
* `range` is the number of degrees to draw the arc.
* `steps` the arc is drawn by dividing it into a number of lines. Defaults to 12.
## Examples
iex> Vivid.Arc.init(Vivid.Point.init(5,5), 4, 45, 15)
%Vivid.Arc{
center: %Vivid.Point{x: 5, y: 5},
radius: 4,
start_angle: 45,
range: 15,
steps: 12
}
"""
@spec init(Point.t(), number, number, number, integer) :: Arc.t()
def init(%Point{} = center, radius, start_angle, range, steps \\ 12)
when is_number(radius) and is_number(start_angle) and is_number(range) and is_integer(steps) do
%Arc{
center: center,
radius: radius,
start_angle: start_angle,
range: range,
steps: steps
}
end
@doc """
Returns the center point of an `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.center
Vivid.Point.init(10, 10)
"""
@spec center(Arc.t()) :: Point.t()
def center(%Arc{center: p} = _arc), do: p
@doc """
Changes the center `point` of `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.center(Vivid.Point.init(15,15))
...> |> Vivid.Arc.center
Vivid.Point.init(15, 15)
"""
@spec center(Arc.t(), Point.t()) :: Arc.t()
def center(%Arc{} = arc, %Point{} = point), do: %{arc | center: point}
@doc """
Returns the radius of an `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.radius
5
"""
@spec radius(Arc.t()) :: number
def radius(%Arc{radius: r} = _arc), do: r
@doc """
Change the `radius` of `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.radius(10)
...> |> Vivid.Arc.radius
10
"""
@spec radius(Arc.t(), number) :: Arc.t()
def radius(%Arc{} = arc, radius) when is_number(radius), do: %{arc | radius: radius}
@doc """
Returns the start angle of an `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.start_angle
0
"""
@spec start_angle(Arc.t()) :: number
def start_angle(%Arc{start_angle: a} = _arc), do: a
@doc """
Change the start angle of an `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.start_angle(45)
...> |> Vivid.Arc.start_angle
45
"""
@spec start_angle(Arc.t(), number) :: Arc.t()
def start_angle(%Arc{} = arc, theta), do: %{arc | start_angle: theta}
@doc """
Returns the range of the `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.range
90
"""
@spec range(Arc.t()) :: number
def range(%Arc{range: r} = _arc), do: r
@doc """
Change the range of an `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.range(270)
...> |> Vivid.Arc.range
270
"""
@spec range(Arc.t(), number) :: Arc.t()
def range(%Arc{} = arc, theta) when is_number(theta), do: %{arc | range: theta}
@doc """
Returns the number of steps in the `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.steps
12
"""
@spec steps(Arc.t()) :: integer
def steps(%Arc{steps: s} = _arc), do: s
@doc """
Changes the number of `steps` in `arc`.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 12)
...> |> Vivid.Arc.steps(19)
...> |> Vivid.Arc.steps
19
"""
@spec steps(Arc.t(), integer) :: Arc.t()
def steps(%Arc{} = arc, steps) when is_integer(steps), do: %{arc | steps: steps}
@doc """
Converts the `arc` into a Path, which is used for a bunch of things like
Transforms, Bounds calculation, Rasterization, etc.
## Example
iex> Vivid.Arc.init(Vivid.Point.init(10,10), 5, 0, 90, 3)
...> |> Vivid.Arc.to_path
Vivid.Path.init([Vivid.Point.init(5, 10), Vivid.Point.init(6, 13), Vivid.Point.init(8, 14), Vivid.Point.init(10, 15)])
"""
@spec to_path(Arc.t()) :: Path.t()
def to_path(
%Arc{center: center, radius: radius, start_angle: start_angle, range: range, steps: steps} =
_arc
) do
h = center |> Point.x()
k = center |> Point.y()
step_degree = range / steps
start_angle = start_angle - 180
points =
Enum.map(0..steps, fn step ->
theta = step_degree * step + start_angle
theta = degrees_to_radians(theta)
x = round(h + radius * cos(theta))
y = round(k - radius * sin(theta))
Point.init(x, y)
end)
points
|> Path.init()
end
end