Improve test and documentation coverage.
This commit is contained in:
parent
f8822fca40
commit
7b29e8f9ae
28 changed files with 605 additions and 74 deletions
16
lib/collectable/vivid/frame.ex
Normal file
16
lib/collectable/vivid/frame.ex
Normal file
|
@ -0,0 +1,16 @@
|
|||
defimpl Collectable, for: Vivid.Frame do
|
||||
alias Vivid.Frame
|
||||
|
||||
@doc """
|
||||
Collect an enumerable into a Frame.
|
||||
"""
|
||||
|
||||
def into(%Frame{shapes: shapes}=frame) do
|
||||
{shapes, fn
|
||||
[], {:cont, {_shape,_colour}=shape} -> [shape]
|
||||
new_shapes, {:cont, {_shape,_colour}=shape} -> [shape | new_shapes]
|
||||
new_shapes, :done -> %{frame | shapes: shapes ++ Enum.reverse(new_shapes)}
|
||||
_, :halt -> :ok
|
||||
end}
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
defmodule Vivid.ShapeToString do
|
||||
alias Vivid.{Bounds, Frame, Transform, RGBA}
|
||||
|
||||
@moduledoc false
|
||||
|
||||
def to_string(shape) do
|
||||
bounds = Bounds.bounds(shape)
|
||||
width = Bounds.width(bounds) + 3 |> round
|
||||
|
|
|
@ -10,6 +10,9 @@ defmodule Vivid do
|
|||
@moduledoc ~S"""
|
||||
Vivid is a 2D rendering engine implemented purely in Elixir.
|
||||
|
||||
If you add `use Vivid` to your module then aliases for all the common Vivid
|
||||
modules will automatically be defined for you.
|
||||
|
||||
## Examples
|
||||
|
||||
Drawing a box on the frame
|
||||
|
|
|
@ -52,7 +52,7 @@ defmodule Vivid.Arc do
|
|||
...> |> Vivid.Arc.center
|
||||
#Vivid.Point<{10, 10}>
|
||||
"""
|
||||
@spec center(Art.t) :: Point.t
|
||||
@spec center(Arc.t) :: Point.t
|
||||
def center(%Arc{center: p}), do: p
|
||||
|
||||
@doc """
|
||||
|
@ -65,7 +65,7 @@ defmodule Vivid.Arc do
|
|||
...> |> Vivid.Arc.center
|
||||
#Vivid.Point<{15, 15}>
|
||||
"""
|
||||
@spec center(Art.t, Point.t) :: Arc.t
|
||||
@spec center(Arc.t, Point.t) :: Arc.t
|
||||
def center(%Arc{}=a, %Point{}=p), do: %{a | center: p}
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
defmodule Vivid.Bounds do
|
||||
alias Vivid.{Bounds, Point}
|
||||
alias Vivid.{Bounds, Point, Shape}
|
||||
defstruct ~w(min max)a
|
||||
|
||||
@moduledoc """
|
||||
Provides information about the bounds of a box and pixel positions within it.
|
||||
"""
|
||||
|
||||
@opaque t :: %Bounds{min: Point.t, max: Point.t}
|
||||
|
||||
@doc """
|
||||
Initialise arbitrary bounds.
|
||||
|
||||
|
@ -14,6 +16,7 @@ defmodule Vivid.Bounds do
|
|||
iex> Vivid.Bounds.init(0, 0, 5, 5)
|
||||
#Vivid.Bounds<[min: #Vivid.Point<{0, 0}>, max: #Vivid.Point<{5, 5}>]>
|
||||
"""
|
||||
@spec init(number, number, number, number) :: Bounds.t
|
||||
def init(x0, y0, x1, y1), do: %Bounds{min: Point.init(x0, y0), max: Point.init(x1, y1)}
|
||||
|
||||
@doc """
|
||||
|
@ -25,6 +28,7 @@ defmodule Vivid.Bounds do
|
|||
...> |> Vivid.Bounds.bounds
|
||||
#Vivid.Bounds<[min: #Vivid.Point<{0.0, 0.0}>, max: #Vivid.Point<{20.0, 20.0}>]>
|
||||
"""
|
||||
@spec bounds(Shape.t) :: Bounds.t
|
||||
def bounds(%Bounds{}=bounds), do: bounds
|
||||
def bounds(shape) do
|
||||
{min, max} = Vivid.Bounds.Of.bounds(shape)
|
||||
|
@ -40,6 +44,7 @@ defmodule Vivid.Bounds do
|
|||
...> |> Vivid.Bounds.width
|
||||
20.0
|
||||
"""
|
||||
@spec width(Shape.t) :: number
|
||||
def width(%Bounds{min: %Point{x: x0}, max: %Point{x: x1}}), do: abs(x1 - x0)
|
||||
def width(shape), do: shape |> bounds |> width
|
||||
|
||||
|
@ -52,6 +57,7 @@ defmodule Vivid.Bounds do
|
|||
...> |> Vivid.Bounds.height
|
||||
20.0
|
||||
"""
|
||||
@spec height(Shape.t) :: number
|
||||
def height(%Bounds{min: %Point{y: y0}, max: %Point{y: y1}}), do: abs(y1 - y0)
|
||||
def height(shape), do: shape |> bounds |> height
|
||||
|
||||
|
@ -64,6 +70,7 @@ defmodule Vivid.Bounds do
|
|||
...> |> Vivid.Bounds.min
|
||||
#Vivid.Point<{0.0, 0.0}>
|
||||
"""
|
||||
@spec min(Shape.t) :: Point.t
|
||||
def min(%Bounds{min: min}), do: min
|
||||
def min(shape), do: shape |> bounds |> min
|
||||
|
||||
|
@ -76,6 +83,7 @@ defmodule Vivid.Bounds do
|
|||
...> |> Vivid.Bounds.max
|
||||
#Vivid.Point<{20.0, 20.0}>
|
||||
"""
|
||||
@spec max(Shape.t) :: Point.t
|
||||
def max(%Bounds{max: max}), do: max
|
||||
def max(shape), do: shape |> bounds |> max
|
||||
|
||||
|
@ -89,6 +97,7 @@ defmodule Vivid.Bounds do
|
|||
...> |> Vivid.Bounds.center_of
|
||||
#Vivid.Point<{10.0, 10.0}>
|
||||
"""
|
||||
@spec center_of(Shape.t) :: Point.t
|
||||
def center_of(%Bounds{min: %Point{x: x0, y: y0}, max: %Point{x: x1, y: y1}}) do
|
||||
x = x0 + (x1 - x0) / 2
|
||||
y = y0 + (y1 - y0) / 2
|
||||
|
@ -125,6 +134,7 @@ defmodule Vivid.Bounds do
|
|||
...> |> Vivid.Bounds.contains?(Vivid.Point.init(11, 11))
|
||||
false
|
||||
"""
|
||||
@spec contains?(Shape.t, Point.t) :: boolean
|
||||
def contains?(%Bounds{min: %Point{x: x0, y: y0}, max: %Point{x: x1, y: y1}}, %Point{x: x, y: y}) when x0 <= x and x <= x1 and y0 <= y and y <= y1, do: true
|
||||
def contains?(_, _), do: false
|
||||
end
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
defprotocol Vivid.Bounds.Of do
|
||||
alias Vivid.{Shape, Point}
|
||||
@moduledoc """
|
||||
This protocol is used to calculate the bounds of a given shape.
|
||||
|
||||
Implement this protocol if you are defining any new shape types.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Return the bounds of a Shape as a two element tuple of bottom-left and
|
||||
top-right points.
|
||||
"""
|
||||
@spec bounds(Shape.t) :: {Point.t, Point.t}
|
||||
def bounds(shape)
|
||||
end
|
|
@ -4,6 +4,7 @@ defimpl Vivid.Bounds.Of, for: Vivid.Group do
|
|||
shapes
|
||||
|> Enum.map(&Vivid.Bounds.Of.bounds(&1))
|
||||
|> Enum.reduce(fn
|
||||
{min, max}, nil -> {min, max}
|
||||
{pmin, pmax}, {min, max} ->
|
||||
min = if pmin.x < min.x, do: Point.init(pmin.x, min.y), else: min
|
||||
min = if pmin.y < min.y, do: Point.init(min.x, pmin.y), else: min
|
||||
|
|
|
@ -1,10 +1,43 @@
|
|||
defmodule Vivid.Box do
|
||||
alias Vivid.{Box, Point, Polygon, Bounds}
|
||||
alias Vivid.{Box, Point, Polygon, Bounds, Shape}
|
||||
defstruct ~w(bottom_left top_right fill)a
|
||||
|
||||
@moduledoc """
|
||||
Short-hand for creating rectangle polygons.
|
||||
|
||||
This module doesn't have very much logic other than knowing how to
|
||||
turn itself into a Polygon.
|
||||
"""
|
||||
|
||||
@opaque t :: Box.t
|
||||
|
||||
@doc """
|
||||
Initialize a Box from it's bottom left and top right points.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> use Vivid
|
||||
...> Box.init(Point.init(1,1), Point.init(4,4))
|
||||
#Vivid.Box<[bottom_left: #Vivid.Point<{1, 1}>, top_right: #Vivid.Point<{4, 4}>]>
|
||||
"""
|
||||
@spec init(Point.t, Point.t) :: Box.t
|
||||
def init(%Point{}=bl, %Point{}=tr), do: init(bl, tr, false)
|
||||
|
||||
@doc false
|
||||
@spec init(Point.t, Point.t, boolean) :: Box.t
|
||||
def init(%Point{}=bl, %Point{}=tr, fill) when is_boolean(fill), do: %Box{bottom_left: bl, top_right: tr, fill: fill}
|
||||
|
||||
@doc """
|
||||
Initialize a box from the bounds of an arbitrary shape.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> use Vivid
|
||||
...> Circle.init(Point.init(5,5), 5)
|
||||
...> |> Box.init_from_bounds
|
||||
#Vivid.Box<[bottom_left: #Vivid.Point<{0.0, 0.2447174185242318}>, top_right: #Vivid.Point<{10.0, 9.755282581475768}>]>
|
||||
"""
|
||||
@spec init_from_bounds(Shape.t) :: Box.t
|
||||
def init_from_bounds(shape, fill \\ false) do
|
||||
bounds = shape |> Bounds.bounds
|
||||
min = bounds |> Bounds.min
|
||||
|
@ -12,11 +45,69 @@ defmodule Vivid.Box do
|
|||
init(min, max, fill)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return the bottom left corner of the box.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> Box.init(Point.init(1,1), Point.init(4,4))
|
||||
...> |> Box.bottom_left
|
||||
#Vivid.Point<{1, 1}>
|
||||
"""
|
||||
@spec bottom_left(Box.t) :: Point.t
|
||||
def bottom_left(%Box{bottom_left: bl}), do: bl
|
||||
|
||||
@doc """
|
||||
Return the top left corner of the box.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> Box.init(Point.init(1,1), Point.init(4,4))
|
||||
...> |> Box.top_left
|
||||
#Vivid.Point<{1, 4}>
|
||||
"""
|
||||
@spec top_left(Box.t) :: Point.t
|
||||
def top_left(%Box{bottom_left: bl, top_right: tr}), do: Point.init(bl.x, tr.y)
|
||||
|
||||
@doc """
|
||||
Return the top right corner of the box.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> Box.init(Point.init(1,1), Point.init(4,4))
|
||||
...> |> Box.top_right
|
||||
#Vivid.Point<{4, 4}>
|
||||
"""
|
||||
@spec top_right(Box.t) :: Point.t
|
||||
def top_right(%Box{top_right: tr}), do: tr
|
||||
|
||||
@doc """
|
||||
Return the top right corner of the box.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> Box.init(Point.init(1,1), Point.init(4,4))
|
||||
...> |> Box.bottom_right
|
||||
#Vivid.Point<{4, 1}>
|
||||
"""
|
||||
@spec bottom_right(Box.t) :: Point.t
|
||||
def bottom_right(%Box{bottom_left: bl, top_right: tr}), do: Point.init(tr.x, bl.y)
|
||||
|
||||
@doc """
|
||||
Convert a Box into a Polygon.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> Box.init(Point.init(1,1), Point.init(4,4))
|
||||
...> |> Box.to_polygon
|
||||
#Vivid.Polygon<[#Vivid.Point<{1, 1}>, #Vivid.Point<{1, 4}>, #Vivid.Point<{4, 4}>, #Vivid.Point<{4, 1}>]>
|
||||
"""
|
||||
@spec to_polygon(Box.t) :: Polygon.t
|
||||
def to_polygon(box) do
|
||||
Polygon.init([
|
||||
bottom_left(box),
|
||||
|
|
|
@ -4,11 +4,32 @@ defmodule Vivid.Buffer do
|
|||
|
||||
@moduledoc """
|
||||
Used to convert a Frame into a buffer for display.
|
||||
|
||||
You're unlikely to need to use this module directly, instead you will
|
||||
likely want to use `Frame.buffer/2` instead.
|
||||
|
||||
Buffer implements the `Enumerable` protocol.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@opaque t :: %Buffer{buffer: [RGBA.t], rows: integer, columns: integer}
|
||||
|
||||
@doc ~S"""
|
||||
Render the buffer horizontally, ie across rows then up columns.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> Frame.init(5, 5, RGBA.white)
|
||||
...> |> Frame.push(Line.init(Point.init(0, 2), Point.init(5, 2)), RGBA.black)
|
||||
...> |> Buffer.horizontal
|
||||
...> |> to_string
|
||||
"@@@@@\n" <>
|
||||
"@@@@@\n" <>
|
||||
" \n" <>
|
||||
"@@@@@\n" <>
|
||||
"@@@@@\n"
|
||||
"""
|
||||
@spec horizontal(Frame.t) :: [RGBA.t]
|
||||
def horizontal(%Frame{shapes: shapes, width: w, height: h}=frame) do
|
||||
buffer = allocate(frame)
|
||||
bounds = Bounds.bounds(frame)
|
||||
|
@ -16,9 +37,23 @@ defmodule Vivid.Buffer do
|
|||
%Buffer{buffer: buffer, rows: h, columns: w}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Render the buffer vertically, ie up columns then across rows.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> Frame.init(5, 5, RGBA.white)
|
||||
...> |> Frame.push(Line.init(Point.init(0, 2), Point.init(5, 2)), RGBA.black)
|
||||
...> |> Buffer.vertical
|
||||
...> |> to_string
|
||||
"@@ @@\n" <>
|
||||
"@@ @@\n" <>
|
||||
"@@ @@\n" <>
|
||||
"@@ @@\n" <>
|
||||
"@@ @@\n"
|
||||
"""
|
||||
@spec vertical(Frame.t) :: [RGBA.t]
|
||||
def vertical(%Frame{shapes: shapes, width: w, height: h}=frame) do
|
||||
bounds = Bounds.bounds(frame)
|
||||
buffer = allocate(frame)
|
||||
|
|
|
@ -7,6 +7,8 @@ defmodule Vivid.Circle do
|
|||
Represents a circle based on it's center point and radius.
|
||||
"""
|
||||
|
||||
@opaque t :: %Circle{center: Point.t, radius: number, fill: boolean}
|
||||
|
||||
@doc """
|
||||
Creates a circle from a point in 2D space and a radius.
|
||||
|
||||
|
@ -15,9 +17,15 @@ defmodule Vivid.Circle do
|
|||
iex> Vivid.Circle.init(Vivid.Point.init(5,5), 4)
|
||||
#Vivid.Circle<[center: #Vivid.Point<{5, 5}>, radius: 4]>
|
||||
"""
|
||||
def init(point, radius), do: init(point, radius, false)
|
||||
@spec init(Point.t, number) :: Circle.t
|
||||
def init(%Point{}=point, radius)
|
||||
when is_number(radius) and radius > 0,
|
||||
do: init(point, radius, false)
|
||||
|
||||
@doc false
|
||||
@spec init(Point.t, number, boolean) :: Circle.t
|
||||
def init(%Point{}=point, radius, fill)
|
||||
when is_number(radius) and is_boolean(fill)
|
||||
when is_number(radius) and is_boolean(fill) and radius > 0
|
||||
do
|
||||
%Circle{
|
||||
center: point,
|
||||
|
@ -31,18 +39,22 @@ defmodule Vivid.Circle do
|
|||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Circle.init(Vivid.Point.init(5,5), 4) |> Vivid.Circle.radius
|
||||
iex> Vivid.Circle.init(Vivid.Point.init(5,5), 4)
|
||||
...> |> Vivid.Circle.radius
|
||||
4
|
||||
"""
|
||||
@spec radius(Cricle.t) :: number
|
||||
def radius(%Circle{radius: r}), do: r
|
||||
|
||||
@doc """
|
||||
Returns the center point of a circle.
|
||||
|
||||
## Example
|
||||
iex> Vivid.Circle.init(Vivid.Point.init(5,5), 4) |> Vivid.Circle.center
|
||||
iex> Vivid.Circle.init(Vivid.Point.init(5,5), 4)
|
||||
...> |> Vivid.Circle.center
|
||||
%Vivid.Point{x: 5, y: 5}
|
||||
"""
|
||||
@spec center(Circle.t) :: Point.t
|
||||
def center(%Circle{center: point}), do: point
|
||||
|
||||
@doc """
|
||||
|
@ -50,12 +62,63 @@ defmodule Vivid.Circle do
|
|||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Circle.init(Vivid.Point.init(5,5), 4) |> Vivid.Circle.circumference
|
||||
iex> Vivid.Circle.init(Vivid.Point.init(5,5), 4)
|
||||
...> |> Vivid.Circle.circumference
|
||||
25.132741228718345
|
||||
"""
|
||||
@spec circumference(Circle.t) :: number
|
||||
def circumference(%Circle{radius: radius}), do: 2 * :math.pi * radius
|
||||
|
||||
@doc ~S"""
|
||||
Convert the circle into a Polygon.
|
||||
|
||||
We convert a circle into a Polygon whenever we Transform or render it, so
|
||||
sometimes it might be worth doing it yourself and specifying how many vertices
|
||||
the polygon should have.
|
||||
|
||||
If unspecified then `steps` is set to the diameter of the circle rounded to
|
||||
the nearest integer.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> use Vivid
|
||||
...> Circle.init(Point.init(5,5), 5)
|
||||
...> |> Circle.to_polygon
|
||||
...> |> to_string
|
||||
"@@@@@@@@@@@@@\n" <>
|
||||
"@@@@ @@@@\n" <>
|
||||
"@@@ @@@@@ @@@\n" <>
|
||||
"@@ @@@@@@@ @@\n" <>
|
||||
"@@ @@@@@@@ @@\n" <>
|
||||
"@ @@@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@@ @\n" <>
|
||||
"@@ @@@@@@@ @@\n" <>
|
||||
"@@ @@@@@@@ @@\n" <>
|
||||
"@@@ @@@@@ @@@\n" <>
|
||||
"@@@@ @@@@\n" <>
|
||||
"@@@@@@@@@@@@@\n"
|
||||
|
||||
iex> use Vivid
|
||||
...> Circle.init(Point.init(5,5), 5)
|
||||
...> |> Circle.to_polygon(3)
|
||||
...> |> to_string
|
||||
"@@@@@@@@@@@\n" <>
|
||||
"@ @@@@@@@@\n" <>
|
||||
"@ @ @@@@@@\n" <>
|
||||
"@ @@@ @@@@\n" <>
|
||||
"@ @@@@@ @@\n" <>
|
||||
"@ @@@@@@@ @\n" <>
|
||||
"@ @@@@@ @@\n" <>
|
||||
"@ @@@ @@@@\n" <>
|
||||
"@ @@ @@@@@@\n" <>
|
||||
"@ @@@@@@@\n" <>
|
||||
"@ @@@@@@@@@\n" <>
|
||||
"@@@@@@@@@@@\n"
|
||||
"""
|
||||
@spec to_polygon(Circle.t) :: Polygon.t
|
||||
def to_polygon(%Circle{radius: radius}=circle), do: to_polygon(circle, round(radius * 2))
|
||||
@spec to_polygon(Circle.t, number) :: Polygon.t
|
||||
def to_polygon(%Circle{center: center, radius: radius, fill: fill}, steps) do
|
||||
h = center |> Point.x
|
||||
k = center |> Point.y
|
||||
|
|
|
@ -1,9 +1,51 @@
|
|||
defmodule Vivid.Font do
|
||||
alias Vivid.{Point, Group}
|
||||
alias Vivid.{Point, Group, Shape}
|
||||
alias Vivid.Font.Char
|
||||
|
||||
@font_vertical_offset 10
|
||||
|
||||
@moduledoc """
|
||||
This module takes characters generated by the Hershey module and converts them
|
||||
into groups of shapes using the character's specified left and right padding.
|
||||
|
||||
Specifically this module only knows about the `rowmans` Hershey font, because
|
||||
it's all I needed. We need a real font layout system. PR's gratefully accepted.
|
||||
"""
|
||||
|
||||
@doc ~S"""
|
||||
Convert a String containing one or more characters into a shape.
|
||||
|
||||
Can only handle characters defined in the `rowmans` Hershey font. Carriage
|
||||
returns and line feeds are also not supported.
|
||||
|
||||
The second argument is a scale factor. Defaults to `1.0`.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> Font.line("hello world", 0.75)
|
||||
...> |> to_string
|
||||
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" <>
|
||||
"@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@ @\n" <>
|
||||
"@ @@@ @@@@@@@@@@ @@@@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@ @@@@@@@ @@@@@@@@ @@ @@@ @@@@@@@@ @@ @\n" <>
|
||||
"@ @@ @@@@ @@@@@@@ @@@@ @@@@@@ @@@@@ @@@@@@ @@@@ @@@@@@@@@@@@@@@@@ @@@@@ @@@@@ @@@@@ @@@@ @@@@@@ @ @@@@@@@ @@@@@@ @@@@ @\n" <>
|
||||
"@ @@@@@@ @@@@@ @@@@@@@ @@@@@ @@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@ @@@ @ @@@ @@@@@ @@@@@@@@ @@@@@ @@@@@@@@ @@@@@ @@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@ @@@@@@@@ @@@@ @@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@ @@@ @ @@@ @@@@@ @@@@@@@@ @@@@@ @@@@@@@@ @@@@@ @@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@ @@@@@@@@ @@@@ @@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@ @@@ @ @@@ @@@@@ @@@@@@@@ @@@@@ @@@@@@@@@ @@@@@ @@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@ @@@@ @@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@ @@@ @ @@@ @@@@@ @@@@@@@@ @@@@@ @@@@@@@@@ @@@@@ @@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@ @@@@@@@@@@@@@ @@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@ @ @@@ @ @@@@@@ @@@@@@@@ @@@@@ @@@@@@@@@ @@@@@ @@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@ @@@@@@@@@@@@@ @@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@ @ @@@ @ @@@@@@ @@@@@@@@ @@@@@ @@@@@@@@@ @@@@@ @@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@ @@@@@@@@ @@@@ @@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@ @ @@@ @ @@@@@@ @@@@@@@@ @@@@@ @@@@@@@@@ @@@@@ @@@@@@@@ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@@ @@@@@@ @@@@@ @@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@ @ @@@ @ @@@@@@@ @@@@@@ @@@@@@ @@@@@@@@@ @@@@@@ @@@@@@ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@@@ @@@@ @@@@@@ @@@@@ @@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@ @@@@ @@@@@@@ @@@@@@@@@ @@@@@@@ @@@@ @ @\n" <>
|
||||
"@ @@@@@@@@ @@@@@@@@ @@@@@@@ @@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@ @@@@@@@@ @@@@@@@@@ @@@@@@@@ @@ @\n" <>
|
||||
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
|
||||
"""
|
||||
@spec line(String.t, number) :: Shape.t
|
||||
def line(str, scale \\ 1.0) do
|
||||
font = rowmans
|
||||
str
|
||||
|
@ -26,6 +68,11 @@ defmodule Vivid.Font do
|
|||
|> Enum.into(Group.init)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the `rowmans` font into a map with the codepoints (characters) as the
|
||||
index, and the font character as the value.
|
||||
"""
|
||||
@spec rowmans() :: map
|
||||
def rowmans do
|
||||
[
|
||||
" ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "↑", ".",
|
||||
|
|
|
@ -1,9 +1,42 @@
|
|||
defmodule Vivid.Frame do
|
||||
alias Vivid.{Frame, RGBA, Buffer}
|
||||
alias Vivid.{Frame, RGBA, Buffer, Shape}
|
||||
defstruct ~w(width height background_colour shapes)a
|
||||
|
||||
@moduledoc """
|
||||
A frame buffer or something.
|
||||
@moduledoc ~S"""
|
||||
Frame represents a collection of colours and shapes.
|
||||
|
||||
Frame implements both the `Enumerable` and `Collectable` protocols.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> use Vivid
|
||||
...> Enum.map(1..5, fn i ->
|
||||
...> line = Line.init(Point.init(1,1), Point.init(20, i * 4))
|
||||
...> {line, RGBA.black}
|
||||
...> end)
|
||||
...> |> Enum.into(Frame.init(24, 21, RGBA.white))
|
||||
...> |> to_string
|
||||
"@@@@@@@@@@@@@@@@@@@@ @@@\n" <>
|
||||
"@@@@@@@@@@@@@@@@@@@ @@@@\n" <>
|
||||
"@@@@@@@@@@@@@@@@@@ @@@@@\n" <>
|
||||
"@@@@@@@@@@@@@@@@@ @@@@@@\n" <>
|
||||
"@@@@@@@@@@@@@@@@ @@@ @@@\n" <>
|
||||
"@@@@@@@@@@@@@@@ @@@ @@@@\n" <>
|
||||
"@@@@@@@@@@@@@@ @@ @@@@@\n" <>
|
||||
"@@@@@@@@@@@@@ @@ @@@@@@@\n" <>
|
||||
"@@@@@@@@@@@@ @@ @@@@ @@@\n" <>
|
||||
"@@@@@@@@@@@ @@ @@@ @@@@\n" <>
|
||||
"@@@@@@@@@@ @ @@ @@@@@@\n" <>
|
||||
"@@@@@@@@@ @ @@ @@@@@@@@\n" <>
|
||||
"@@@@@@@@ @ @@ @@@@@ @@@\n" <>
|
||||
"@@@@@@@ @ @@@ @@@@@\n" <>
|
||||
"@@@@@@ @ @@@ @@@@@@@@\n" <>
|
||||
"@@@@@ @ @@ @@@@@@@@@@\n" <>
|
||||
"@@@@ @@@@@@ @@@\n" <>
|
||||
"@@@ @@@ @@@@@@@\n" <>
|
||||
"@@ @@@@@@@@@@@@@\n" <>
|
||||
"@ @@@@@@@@@@@@@@@@@@@\n" <>
|
||||
"@@@@@@@@@@@@@@@@@@@@@@@@\n"
|
||||
"""
|
||||
|
||||
@opaque t :: %Frame{width: integer, height: integer, background_colour: RGBA.t, shapes: []}
|
||||
|
@ -166,7 +199,7 @@ defmodule Vivid.Frame do
|
|||
and in `:vertical` mode the buffer is rendered column-by-column then
|
||||
row-by-row.
|
||||
|
||||
Returns a one-dimensional List of %RGBA{} colours with alpha-compositing
|
||||
Returns a one-dimensional List of `RGBA` colours with alpha-compositing
|
||||
completed.
|
||||
"""
|
||||
@spec buffer(Frame.t) :: [RGBA.t]
|
||||
|
|
|
@ -1,28 +1,39 @@
|
|||
defmodule Vivid.Group do
|
||||
alias Vivid.Group
|
||||
alias Vivid.{Group, Shape}
|
||||
defstruct ~w(shapes)a
|
||||
|
||||
@moduledoc """
|
||||
Represents a collection of shapes which can be Rasterized in a single pass.
|
||||
|
||||
Group implements both the `Enumerable` and `Collectable` protocols.
|
||||
"""
|
||||
|
||||
@opaque t :: %Group{shapes: [Shape.t]}
|
||||
|
||||
@doc """
|
||||
Initialize a group either empty or from a list of shapes.
|
||||
Initialize an empty group.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Vivid.Group.init
|
||||
#Vivid.Group<[]>
|
||||
"""
|
||||
@spec init() :: Group.t
|
||||
def init, do: %Group{shapes: MapSet.new()}
|
||||
|
||||
@doc """
|
||||
Initialize a group from a list of shapes.
|
||||
|
||||
## Example
|
||||
|
||||
iex> circle = Vivid.Circle.init(Vivid.Point.init(5,5), 5)
|
||||
...> line = Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(10,10))
|
||||
...> Vivid.Group.init([circle, line])
|
||||
#Vivid.Group<[#Vivid.Line<[origin: #Vivid.Point<{1, 1}>, termination: #Vivid.Point<{10, 10}>]>, #Vivid.Circle<[center: #Vivid.Point<{5, 5}>, radius: 5]>]>
|
||||
|
||||
iex> Vivid.Group.init
|
||||
%Vivid.Group{shapes: MapSet.new()}
|
||||
"""
|
||||
|
||||
def init, do: %Group{shapes: MapSet.new()}
|
||||
@spec init([Shape.t]) :: Group.t
|
||||
def init(shapes) do
|
||||
%Group{shapes: MapSet.new(shapes)}
|
||||
%Group{shapes: Enum.into(shapes, MapSet.new)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -35,6 +46,7 @@ defmodule Vivid.Group do
|
|||
...> |> Vivid.Group.delete(line)
|
||||
%Vivid.Group{shapes: MapSet.new()}
|
||||
"""
|
||||
@spec delete(Group.t, Shape.t) :: Group.t
|
||||
def delete(%Group{shapes: shapes}, shape), do: shapes |> MapSet.delete(shape) |> init
|
||||
|
||||
@doc """
|
||||
|
@ -49,5 +61,6 @@ defmodule Vivid.Group do
|
|||
%Vivid.Line{origin: %Vivid.Point{x: 1, y: 1}, termination: %Vivid.Point{x: 10, y: 10}}
|
||||
])}
|
||||
"""
|
||||
@spec put(Group.t, Shape.t) :: Group.t
|
||||
def put(%Group{shapes: shapes}, shape), do: shapes |> MapSet.put(shape) |> init
|
||||
end
|
|
@ -7,6 +7,8 @@ defmodule Vivid.Line do
|
|||
Represents a line segment between two Points in 2D space.
|
||||
"""
|
||||
|
||||
@opaque t :: %Line{origin: Point.t, termination: Point.t}
|
||||
|
||||
@doc ~S"""
|
||||
Creates a Line.
|
||||
|
||||
|
@ -16,9 +18,12 @@ defmodule Vivid.Line do
|
|||
%Vivid.Line{origin: %Vivid.Point{x: 1, y: 1}, termination: %Vivid.Point{x: 4, y: 4}}
|
||||
|
||||
"""
|
||||
@spec init(Point.t, Point.t) :: Line.t
|
||||
def init(%Point{}=o, %Point{}=t) do
|
||||
%Line{origin: o, termination: t}
|
||||
end
|
||||
|
||||
@spec init([Point.t]) :: Line.t
|
||||
def init([o,t]) do
|
||||
init(o,t)
|
||||
end
|
||||
|
@ -31,6 +36,7 @@ defmodule Vivid.Line do
|
|||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(4,4)) |> Vivid.Line.origin
|
||||
%Vivid.Point{x: 1, y: 1}
|
||||
"""
|
||||
@spec origin(Line.t) :: Point.t
|
||||
def origin(%Line{origin: o}), do: o
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -41,6 +47,7 @@ defmodule Vivid.Line do
|
|||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(4,4)) |> Vivid.Line.termination
|
||||
%Vivid.Point{x: 4, y: 4}
|
||||
"""
|
||||
@spec termination(Line.t) :: Point.t
|
||||
def termination(%Line{termination: t}), do: t
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -51,6 +58,7 @@ defmodule Vivid.Line do
|
|||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(14,4)) |> Vivid.Line.width
|
||||
13
|
||||
"""
|
||||
@spec width(Line.t) :: number
|
||||
def width(%Line{}=line), do: abs(x_distance(line))
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -61,6 +69,7 @@ defmodule Vivid.Line do
|
|||
iex> Vivid.Line.init(Vivid.Point.init(14,1), Vivid.Point.init(1,4)) |> Vivid.Line.x_distance
|
||||
-13
|
||||
"""
|
||||
@spec x_distance(Line.t) :: number
|
||||
def x_distance(%Line{origin: %Point{x: x0}, termination: %Point{x: x1}}), do: x1 - x0
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -71,7 +80,7 @@ defmodule Vivid.Line do
|
|||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(4,14)) |> Vivid.Line.height
|
||||
13
|
||||
"""
|
||||
|
||||
@spec height(Line.t) :: number
|
||||
def height(%Line{}=line), do: abs(y_distance(line))
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -82,6 +91,7 @@ defmodule Vivid.Line do
|
|||
iex> Vivid.Line.init(Vivid.Point.init(1,14), Vivid.Point.init(4,1)) |> Vivid.Line.y_distance
|
||||
-13
|
||||
"""
|
||||
@spec y_distance(Line.t) :: number
|
||||
def y_distance(%Line{origin: %Point{y: y0}, termination: %Point{y: y1}}), do: y1 - y0
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -93,6 +103,7 @@ defmodule Vivid.Line do
|
|||
iex> Vivid.Line.init(Vivid.Point.init(1,1), Vivid.Point.init(4,5)) |> Vivid.Line.length
|
||||
5.0
|
||||
"""
|
||||
@spec length(Line.t) :: number
|
||||
def length(%Line{}=line) do
|
||||
dx2 = line |> width |> pow(2)
|
||||
dy2 = line |> height |> pow(2)
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
defmodule Vivid.Math do
|
||||
@moduledoc """
|
||||
I made this because I was constantly importing a small selection of
|
||||
Erlang's `:math` module, and then manually implementing
|
||||
`degrees_to_radians/1` which got pretty annoying after a while.
|
||||
"""
|
||||
|
||||
defdelegate pi(), to: :math
|
||||
defdelegate cos(x), to: :math
|
||||
defdelegate sin(x), to: :math
|
||||
defdelegate pow(x,y), to: :math
|
||||
defdelegate sqrt(x), to: :math
|
||||
|
||||
@doc """
|
||||
Convert degrees into radians.
|
||||
|
||||
## Examples:
|
||||
|
||||
iex> 180 |> Vivid.Math.degrees_to_radians
|
||||
:math.pi
|
||||
"""
|
||||
def degrees_to_radians(degrees), do: degrees / 360.0 * 2.0 * pi
|
||||
end
|
|
@ -1,15 +1,30 @@
|
|||
defmodule Vivid.Path do
|
||||
alias Vivid.{Path, Point, Line}
|
||||
alias Vivid.{Path, Point, Line, Shape}
|
||||
defstruct vertices: []
|
||||
|
||||
@moduledoc """
|
||||
Describes a path as a series of vertices.
|
||||
|
||||
Path implements both the `Enumerable` and `Collectable` protocols.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Initialize a path either empty or from a list of points.
|
||||
@opaque t :: %Path{vertices: [Shape.t]}
|
||||
|
||||
## Examples
|
||||
@doc """
|
||||
Initialize an empty path.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Path.init
|
||||
%Vivid.Path{vertices: []}
|
||||
"""
|
||||
@spec init() :: Path.t
|
||||
def init, do: %Path{vertices: []}
|
||||
|
||||
@doc """
|
||||
Initialize a path from a list of points.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Path.init([Vivid.Point.init(1,1), Vivid.Point.init(1,2), Vivid.Point.init(2,2), Vivid.Point.init(2,1)])
|
||||
%Vivid.Path{vertices: [
|
||||
|
@ -18,13 +33,9 @@ defmodule Vivid.Path do
|
|||
%Vivid.Point{x: 2, y: 2},
|
||||
%Vivid.Point{x: 2, y: 1}
|
||||
]}
|
||||
|
||||
iex> Vivid.Path.init
|
||||
%Vivid.Path{vertices: []}
|
||||
"""
|
||||
|
||||
@spec init([Point.t]) :: Path.t
|
||||
def init(points) when is_list(points), do: %Path{vertices: points}
|
||||
def init, do: %Path{vertices: []}
|
||||
|
||||
@doc """
|
||||
Convert a path into a list of lines joined by the vertices.
|
||||
|
@ -39,6 +50,7 @@ defmodule Vivid.Path do
|
|||
%Vivid.Line{origin: %Vivid.Point{x: 2, y: 2},
|
||||
termination: %Vivid.Point{x: 2, y: 1}}]
|
||||
"""
|
||||
@spec to_lines(Path.t) :: [Line.t]
|
||||
def to_lines(%Path{vertices: points}) do
|
||||
points_to_lines([], points)
|
||||
end
|
||||
|
@ -51,6 +63,7 @@ defmodule Vivid.Path do
|
|||
iex> Vivid.Path.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)]) |> Vivid.Path.delete(Vivid.Point.init(2,2))
|
||||
%Vivid.Path{vertices: [%Vivid.Point{x: 1, y: 1}]}
|
||||
"""
|
||||
@spec delete(Path.t, Point.t) :: Path.t
|
||||
def delete(%Path{vertices: points}, %Point{}=point) do
|
||||
points
|
||||
|> List.delete(point)
|
||||
|
@ -65,6 +78,7 @@ defmodule Vivid.Path do
|
|||
iex> Vivid.Path.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)]) |> Vivid.Path.delete_at(1)
|
||||
%Vivid.Path{vertices: [%Vivid.Point{x: 1, y: 1}]}
|
||||
"""
|
||||
@spec delete_at(Path.t, integer) :: Path.t
|
||||
def delete_at(%Path{vertices: points}, index) do
|
||||
points
|
||||
|> List.delete_at(index)
|
||||
|
@ -72,20 +86,21 @@ defmodule Vivid.Path do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Remove a vertex at a specific index in the Path.
|
||||
Return the first vertex in the Path.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Path.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)]) |> Vivid.Path.first
|
||||
%Vivid.Point{x: 1, y: 1}
|
||||
"""
|
||||
@spec first(Path.t) :: Point.t
|
||||
def first(%Path{vertices: points}) do
|
||||
points
|
||||
|> List.first
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove a vertex at a specific index in the Path.
|
||||
Insert a vertex at a specific index in the Path.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -96,6 +111,7 @@ defmodule Vivid.Path do
|
|||
%Vivid.Point{x: 2, y: 2}
|
||||
]}
|
||||
"""
|
||||
@spec insert_at(Path.t, integer, Point.t) :: Path.t
|
||||
def insert_at(%Path{vertices: points}, index, %Point{}=point) do
|
||||
points
|
||||
|> List.insert_at(index, point)
|
||||
|
@ -103,20 +119,21 @@ defmodule Vivid.Path do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Remove a vertex at a specific index in the Path.
|
||||
Return the last vertex in the Path.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Path.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)]) |> Vivid.Path.last
|
||||
%Vivid.Point{x: 2, y: 2}
|
||||
"""
|
||||
@spec last(Path.t) :: Point.t
|
||||
def last(%Path{vertices: points}) do
|
||||
points
|
||||
|> List.last
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove a vertex at a specific index in the Path.
|
||||
Replace a vertex at a specific index in the Path.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -127,20 +144,21 @@ defmodule Vivid.Path do
|
|||
%Vivid.Point{x: 3, y: 3}
|
||||
]}
|
||||
"""
|
||||
@spec replace_at(Path.t, integer, Point.t) :: Path.t
|
||||
def replace_at(%Path{vertices: points}, index, %Point{}=point) do
|
||||
points
|
||||
|> List.replace_at(index, point)
|
||||
|> init
|
||||
end
|
||||
|
||||
def points_to_lines(lines, []), do: lines
|
||||
defp points_to_lines(lines, []), do: lines
|
||||
|
||||
def points_to_lines([], [origin | [term | points]]) do
|
||||
defp points_to_lines([], [origin | [term | points]]) do
|
||||
line = Line.init(origin, term)
|
||||
points_to_lines([line], points)
|
||||
end
|
||||
|
||||
def points_to_lines(lines, [point | rest]) do
|
||||
defp points_to_lines(lines, [point | rest]) do
|
||||
origin = lines |> List.last |> Line.termination
|
||||
term = point
|
||||
lines = lines ++ [Line.init(origin, term)]
|
||||
|
|
|
@ -6,6 +6,8 @@ defmodule Vivid.Point do
|
|||
Represents an individual point in (2D) space.
|
||||
"""
|
||||
|
||||
@opaque t :: %Point{x: number, y: number}
|
||||
|
||||
@doc ~S"""
|
||||
Creates a Point.
|
||||
|
||||
|
@ -14,7 +16,10 @@ defmodule Vivid.Point do
|
|||
iex> Vivid.Point.init(13, 27)
|
||||
%Vivid.Point{x: 13, y: 27}
|
||||
"""
|
||||
def init(x, y) do
|
||||
@spec init(number, number) :: Point.t
|
||||
def init(x, y)
|
||||
when is_number(x) and is_number(y)
|
||||
do
|
||||
%Point{x: x, y: y}
|
||||
end
|
||||
|
||||
|
@ -26,6 +31,7 @@ defmodule Vivid.Point do
|
|||
iex> Vivid.Point.init(13, 27) |> Vivid.Point.x
|
||||
13
|
||||
"""
|
||||
@spec x(Point.t) :: number
|
||||
def x(%Point{x: x}), do: x
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -36,17 +42,34 @@ defmodule Vivid.Point do
|
|||
iex> Vivid.Point.init(13, 27) |> Vivid.Point.y
|
||||
27
|
||||
"""
|
||||
@spec y(Point.t) :: number
|
||||
def y(%Point{y: y}), do: y
|
||||
|
||||
@doc """
|
||||
Simple helper to swap X and Y coordinates - used
|
||||
when translating the frame buffer to vertical.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Point.init(13, 27)
|
||||
...> |> Vivid.Point.swap_xy
|
||||
#Vivid.Point<{27, 13}>
|
||||
"""
|
||||
@spec swap_xy(Point.t) :: Point.t
|
||||
def swap_xy(%Point{x: x, y: y}), do: Point.init(y, x)
|
||||
|
||||
@doc """
|
||||
Return the vector in `x` and `y` between point `a` and point `b`.
|
||||
|
||||
## Example
|
||||
|
||||
iex> use Vivid
|
||||
...> a = Point.init(10, 10)
|
||||
...> b = Point.init(20, 20)
|
||||
...> Point.vector(a, b)
|
||||
{10, 10}
|
||||
"""
|
||||
@spec vector(Point.t, Point.t) :: {number, number}
|
||||
def vector(%Point{x: x0, y: y0}, %Point{x: x1, y: y1}) do
|
||||
{x1 - x0, y1 - y0}
|
||||
end
|
||||
|
@ -60,5 +83,6 @@ defmodule Vivid.Point do
|
|||
...> |> Vivid.Point.round
|
||||
#Vivid.Point<{1, 5}>
|
||||
"""
|
||||
@spec round(Point.t) :: Point.t
|
||||
def round(%Point{x: x, y: y}), do: Point.init(Kernel.round(x), Kernel.round(y))
|
||||
end
|
|
@ -4,12 +4,27 @@ defmodule Vivid.Polygon do
|
|||
|
||||
@moduledoc """
|
||||
Describes a Polygon as a series of vertices.
|
||||
|
||||
Polygon implements both the `Enumerable` and `Collectable` protocols.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Initialize a Polygon either empty or from a list of points.
|
||||
@opaque t :: %Polygon{vertices: [Point.t], fill: boolean}
|
||||
|
||||
## Examples
|
||||
@doc """
|
||||
Initialize an empty Polygon.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Polygon.init
|
||||
%Vivid.Polygon{vertices: []}
|
||||
"""
|
||||
@spec init() :: Polygon.t
|
||||
def init, do: %Polygon{vertices: [], fill: false}
|
||||
|
||||
@doc """
|
||||
Initialize a Polygon from a list of points.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Polygon.init([Vivid.Point.init(1,1), Vivid.Point.init(1,2), Vivid.Point.init(2,2), Vivid.Point.init(2,1)])
|
||||
%Vivid.Polygon{vertices: [
|
||||
|
@ -18,13 +33,12 @@ defmodule Vivid.Polygon do
|
|||
%Vivid.Point{x: 2, y: 2},
|
||||
%Vivid.Point{x: 2, y: 1}
|
||||
]}
|
||||
|
||||
iex> Vivid.Polygon.init
|
||||
%Vivid.Polygon{vertices: []}
|
||||
"""
|
||||
|
||||
def init, do: %Polygon{vertices: [], fill: false}
|
||||
@spec init([Point.t]) :: Polygon.t
|
||||
def init(points) when is_list(points), do: %Polygon{vertices: points, fill: false}
|
||||
|
||||
@doc false
|
||||
@spec init([Point.t], boolean) :: Polygon.t
|
||||
def init(points, fill) when is_list(points) and is_boolean(fill), do: %Polygon{vertices: points, fill: fill}
|
||||
|
||||
@doc """
|
||||
|
@ -42,6 +56,7 @@ defmodule Vivid.Polygon do
|
|||
%Vivid.Line{origin: %Vivid.Point{x: 2, y: 1},
|
||||
termination: %Vivid.Point{x: 1, y: 1}}]
|
||||
"""
|
||||
@spec to_lines(Polygon.t) :: [Line.t]
|
||||
def to_lines(%Polygon{vertices: points}) do
|
||||
points_to_lines([], points)
|
||||
end
|
||||
|
@ -54,6 +69,7 @@ defmodule Vivid.Polygon do
|
|||
iex> Vivid.Polygon.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)]) |> Vivid.Polygon.delete(Vivid.Point.init(2,2))
|
||||
%Vivid.Polygon{vertices: [%Vivid.Point{x: 1, y: 1}]}
|
||||
"""
|
||||
@spec delete(Polygon.t, Point.t) :: Polygon.t
|
||||
def delete(%Polygon{vertices: points}, %Point{}=point) do
|
||||
points
|
||||
|> List.delete(point)
|
||||
|
@ -68,6 +84,7 @@ defmodule Vivid.Polygon do
|
|||
iex> Vivid.Polygon.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)]) |> Vivid.Polygon.delete_at(1)
|
||||
%Vivid.Polygon{vertices: [%Vivid.Point{x: 1, y: 1}]}
|
||||
"""
|
||||
@spec delete_at(Polygon.t, integer) :: Polygon.t
|
||||
def delete_at(%Polygon{vertices: points}, index) do
|
||||
points
|
||||
|> List.delete_at(index)
|
||||
|
@ -75,20 +92,21 @@ defmodule Vivid.Polygon do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Remove a vertex at a specific index in the Polygon.
|
||||
Return the first vertex in the Polygon.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Polygon.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)]) |> Vivid.Polygon.first
|
||||
%Vivid.Point{x: 1, y: 1}
|
||||
"""
|
||||
@spec first(Polygon.t) :: Point.t
|
||||
def first(%Polygon{vertices: points}) do
|
||||
points
|
||||
|> List.first
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove a vertex at a specific index in the Polygon.
|
||||
Insert a vertex at a specific index in the Polygon.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -99,6 +117,7 @@ defmodule Vivid.Polygon do
|
|||
%Vivid.Point{x: 2, y: 2}
|
||||
]}
|
||||
"""
|
||||
@spec insert_at(Polygon.t, integer, Point.t) :: Polygon.t
|
||||
def insert_at(%Polygon{vertices: points}, index, %Point{}=point) do
|
||||
points
|
||||
|> List.insert_at(index, point)
|
||||
|
@ -106,20 +125,21 @@ defmodule Vivid.Polygon do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Remove a vertex at a specific index in the Polygon.
|
||||
Return the last vertext in the Polygon.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Polygon.init([Vivid.Point.init(1,1), Vivid.Point.init(2,2)]) |> Vivid.Polygon.last
|
||||
%Vivid.Point{x: 2, y: 2}
|
||||
"""
|
||||
@spec last(Polygon.t) :: Point.t
|
||||
def last(%Polygon{vertices: points}) do
|
||||
points
|
||||
|> List.last
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove a vertex at a specific index in the Polygon.
|
||||
Replace a vertex at a specific index in the Polygon.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -130,24 +150,25 @@ defmodule Vivid.Polygon do
|
|||
%Vivid.Point{x: 3, y: 3}
|
||||
]}
|
||||
"""
|
||||
@spec replace_at(Polygon.t, integer, Point.t) :: Polygon.t
|
||||
def replace_at(%Polygon{vertices: points}, index, %Point{}=point) do
|
||||
points
|
||||
|> List.replace_at(index, point)
|
||||
|> init
|
||||
end
|
||||
|
||||
def points_to_lines(lines, []) do
|
||||
defp points_to_lines(lines, []) do
|
||||
origin = lines |> List.last |> Line.termination
|
||||
term = lines |> List.first |> Line.origin
|
||||
lines ++ [Line.init(origin, term)]
|
||||
end
|
||||
|
||||
def points_to_lines([], [origin | [term | points]]) do
|
||||
defp points_to_lines([], [origin | [term | points]]) do
|
||||
line = Line.init(origin, term)
|
||||
points_to_lines([line], points)
|
||||
end
|
||||
|
||||
def points_to_lines(lines, [point | rest]) do
|
||||
defp points_to_lines(lines, [point | rest]) do
|
||||
origin = lines |> List.last |> Line.termination
|
||||
term = point
|
||||
lines = lines ++ [Line.init(origin, term)]
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
defprotocol Vivid.Rasterize do
|
||||
alias Vivid.Shape
|
||||
@moduledoc """
|
||||
The Rasterize protocol is responsible for converting shapes into bitmaps.
|
||||
|
||||
If you're defining your own shape then you need to implement this protocol.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Convert a shape into a bitmap.
|
||||
|
||||
Takes a `shape` and returns a `MapSet` of points within `bounds`.
|
||||
"""
|
||||
@spec rasterize(Shape.t, Bounds.t) :: MapSet
|
||||
def rasterize(shape, bounds)
|
||||
end
|
|
@ -7,8 +7,19 @@ defmodule Vivid.RGBA do
|
|||
|
||||
@moduledoc """
|
||||
Defines a colour in RGBA colour space.
|
||||
|
||||
Colour and alpha values are defined as `0 >= n >= 1`.
|
||||
"""
|
||||
|
||||
@type zero_to_one :: number
|
||||
@opaque t :: %RGBA{red: zero_to_one,
|
||||
green: zero_to_one,
|
||||
blue: zero_to_one,
|
||||
alpha: zero_to_one,
|
||||
a_red: zero_to_one,
|
||||
a_green: zero_to_one,
|
||||
a_blue: zero_to_one}
|
||||
|
||||
@doc """
|
||||
Create a colour. Like magic.
|
||||
|
||||
|
@ -17,9 +28,10 @@ defmodule Vivid.RGBA do
|
|||
iex> Vivid.RGBA.init(0.1, 0.2, 0.3, 0.4)
|
||||
#Vivid.RGBA<{0.1, 0.2, 0.3, 0.4}>
|
||||
"""
|
||||
|
||||
@spec init(zero_to_one, zero_to_one, zero_to_one) :: RGBA.t
|
||||
def init(red, green, blue), do: init(red, green, blue, 1)
|
||||
|
||||
@spec init(zero_to_one, zero_to_one, zero_to_one, zero_to_one) :: RGBA.t
|
||||
def init(red, green, blue, 1)
|
||||
when is_number(red) and is_number(green) and is_number(blue)
|
||||
and red >= 0 and red <= 1
|
||||
|
@ -80,6 +92,7 @@ defmodule Vivid.RGBA do
|
|||
iex> Vivid.RGBA.white
|
||||
#Vivid.RGBA<{1, 1, 1, 1}>
|
||||
"""
|
||||
@spec white() :: RGBA.t
|
||||
def white, do: RGBA.init(1,1,1)
|
||||
|
||||
@doc """
|
||||
|
@ -90,6 +103,7 @@ defmodule Vivid.RGBA do
|
|||
iex> Vivid.RGBA.black
|
||||
#Vivid.RGBA<{0, 0, 0, 1}>
|
||||
"""
|
||||
@spec black() :: RGBA.t
|
||||
def black, do: RGBA.init(0,0,0)
|
||||
|
||||
@doc """
|
||||
|
@ -101,7 +115,8 @@ defmodule Vivid.RGBA do
|
|||
...> |> Vivid.RGBA.red
|
||||
0.7
|
||||
"""
|
||||
def red(%RGBA{red: r}), do: r
|
||||
@spec red(RGBA.t) :: zero_to_one
|
||||
def red(%RGBA{red: r}), do: r
|
||||
|
||||
@doc """
|
||||
Return the green component of the colour.
|
||||
|
@ -112,6 +127,7 @@ defmodule Vivid.RGBA do
|
|||
...> |> Vivid.RGBA.green
|
||||
0.6
|
||||
"""
|
||||
@spec green(RGBA.t) :: zero_to_one
|
||||
def green(%RGBA{green: g}), do: g
|
||||
|
||||
@doc """
|
||||
|
@ -123,7 +139,8 @@ defmodule Vivid.RGBA do
|
|||
...> |> Vivid.RGBA.blue
|
||||
0.5
|
||||
"""
|
||||
def blue(%RGBA{blue: b}), do: b
|
||||
@spec blue(RGBA.t) :: zero_to_one
|
||||
def blue(%RGBA{blue: b}), do: b
|
||||
|
||||
@doc """
|
||||
Return the alpha component of the colour.
|
||||
|
@ -134,6 +151,7 @@ defmodule Vivid.RGBA do
|
|||
...> |> Vivid.RGBA.alpha
|
||||
0.4
|
||||
"""
|
||||
@spec alpha(RGBA.t) :: zero_to_one
|
||||
def alpha(%RGBA{alpha: a}), do: a
|
||||
|
||||
@doc """
|
||||
|
@ -145,6 +163,7 @@ defmodule Vivid.RGBA do
|
|||
...> |> Vivid.RGBA.to_hex
|
||||
"#B39980"
|
||||
"""
|
||||
@spec to_hex(RGBA.t) :: String.t
|
||||
def to_hex(%RGBA{red: r, green: g, blue: b, alpha: 1}) do
|
||||
r = r |> f2h
|
||||
g = g |> f2h
|
||||
|
@ -168,7 +187,7 @@ defmodule Vivid.RGBA do
|
|||
iex> Vivid.RGBA.over(Vivid.RGBA.black, Vivid.RGBA.init(1,1,1, 0.5))
|
||||
#Vivid.RGBA<{0.5, 0.5, 0.5, 1.0}>
|
||||
"""
|
||||
|
||||
@spec over(RGBA.t, RGBA.t) :: RGBA.t
|
||||
def over(nil, %RGBA{}=colour), do: colour
|
||||
def over(%RGBA{}, %RGBA{alpha: 1}=visible), do: visible
|
||||
def over(%RGBA{}=visible, %RGBA{alpha: 0}), do: visible
|
||||
|
@ -196,11 +215,22 @@ defmodule Vivid.RGBA do
|
|||
iex> Vivid.RGBA.black |> Vivid.RGBA.luminance
|
||||
0.0
|
||||
"""
|
||||
@spec luminance(RGBA.t) :: zero_to_one
|
||||
def luminance(%RGBA{a_red: r, a_green: g, a_blue: b}) do
|
||||
[rl, gl, bl] = [r, g, b ] |> Enum.map(&pow(&1, 2.2))
|
||||
0.2128 * rl + 0.7150 * gl + 0.0722 * bl
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a colour to an ASCII character.
|
||||
|
||||
This isn't very scientific, but helps with debugging and is used in the
|
||||
implementations of `String.Chars` for Vivid types.
|
||||
|
||||
The chacaters used (from black to white) are `" .:-=+*#%@"`. These are
|
||||
chosen based on the `luminance/1` value of the colour.
|
||||
"""
|
||||
@spec to_ascii(RGBA.t) :: String.t
|
||||
def to_ascii(%RGBA{}=colour) do
|
||||
l = luminance(colour)
|
||||
c = l * (@ascii_luminance_map_length - 1) |> round
|
||||
|
|
8
lib/vivid/shape.ex
Normal file
8
lib/vivid/shape.ex
Normal file
|
@ -0,0 +1,8 @@
|
|||
defmodule Vivid.Shape do
|
||||
alias Vivid.{Arc, Bounds, Box, Circle, Group, Line, Path, Point, Polygon}
|
||||
@moduledoc """
|
||||
Doesn't do anything - is merely a type to represent an arbitrary shape.
|
||||
"""
|
||||
|
||||
@type t :: Arc.t | Bounds.t | Box.t | Circle.t | Group.t | Line.t | Path.t | Point.t | Polygon.t
|
||||
end
|
|
@ -1,11 +1,16 @@
|
|||
defmodule Vivid.Transform do
|
||||
alias Vivid.{Point, Transform, Bounds}
|
||||
alias Vivid.{Point, Transform, Bounds, Shape}
|
||||
alias Vivid.Transformable
|
||||
import Vivid.Math
|
||||
defstruct [operations: [], shape: nil]
|
||||
|
||||
defmodule Operation do
|
||||
alias __MODULE__
|
||||
defstruct ~w(function name)a
|
||||
|
||||
@moduledoc false
|
||||
|
||||
@opaque t :: %Operation{function: function, name: String.t}
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
|
@ -25,6 +30,10 @@ defmodule Vivid.Transform do
|
|||
#Vivid.Polygon<[#Vivid.Point<{30.106601717798213, 21.696699141100893}>, #Vivid.Point<{19.5, 24.803300858899107}>, #Vivid.Point<{8.893398282201787, 17.303300858899107}>, #Vivid.Point<{19.5, 14.196699141100893}>]>
|
||||
"""
|
||||
|
||||
@opaque t :: %Transform{shape: Shape.t, operations: [Operation.t]}
|
||||
@type shape_or_transform :: Transform.t | Shape.t
|
||||
@type degrees :: number
|
||||
|
||||
@doc """
|
||||
Translate (ie move) a shape by adding `x` and `y` to each Point.
|
||||
|
||||
|
@ -35,6 +44,7 @@ defmodule Vivid.Transform do
|
|||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{15, 10}>, #Vivid.Point<{15, 15}>, #Vivid.Point<{10, 15}>, #Vivid.Point<{10, 10}>]>
|
||||
"""
|
||||
@spec translate(shape_or_transform, number, number) :: Transform.t
|
||||
def translate(shape, x, y) do
|
||||
fun = fn _shape ->
|
||||
&Transform.Point.translate(&1, x, y)
|
||||
|
@ -59,6 +69,7 @@ defmodule Vivid.Transform do
|
|||
#Vivid.Polygon<[#Vivid.Point<{12.5, -2.5}>, #Vivid.Point<{12.5, 17.5}>, #Vivid.Point<{2.5, 17.5}>, #Vivid.Point<{2.5, -2.5}>]>
|
||||
|
||||
"""
|
||||
@spec scale(shape_or_transform, number) :: Transform.t
|
||||
def scale(shape, uniform) do
|
||||
fun = fn shape ->
|
||||
origin = Bounds.center_of(shape)
|
||||
|
@ -68,6 +79,7 @@ defmodule Vivid.Transform do
|
|||
apply_transform(shape, fun, "scale-#{uniform}x")
|
||||
end
|
||||
|
||||
@spec scale(shape_or_transform, number, number) :: Transform.t
|
||||
def scale(shape, x, y) do
|
||||
fun = fn shape ->
|
||||
origin = Bounds.center_of(shape)
|
||||
|
@ -78,7 +90,7 @@ defmodule Vivid.Transform do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Rotate a shape around an origin point. The default point the shape's center.
|
||||
Rotate a shape around it's center point.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -86,12 +98,8 @@ defmodule Vivid.Transform do
|
|||
...> |> Vivid.Transform.rotate(45)
|
||||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{22.071067811865476, 16.464466094067262}>, #Vivid.Point<{15.0, 18.535533905932738}>, #Vivid.Point<{7.9289321881345245, 13.535533905932738}>, #Vivid.Point<{15.0, 11.464466094067262}>]>
|
||||
|
||||
iex> Vivid.Box.init(Vivid.Point.init(10,10), Vivid.Point.init(20,20))
|
||||
...> |> Vivid.Transform.rotate(45, Vivid.Point.init(5,5))
|
||||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{12.071067811865476, 13.535533905932738}>, #Vivid.Point<{5.000000000000002, 15.606601717798215}>, #Vivid.Point<{-2.0710678118654737, 10.606601717798215}>, #Vivid.Point<{5.0, 8.535533905932738}>]>
|
||||
"""
|
||||
@spec rotate(shape_or_transform, degrees) :: Transform.t
|
||||
def rotate(shape, degrees) do
|
||||
radians = degrees_to_radians(degrees)
|
||||
fun = fn shape ->
|
||||
|
@ -101,6 +109,18 @@ defmodule Vivid.Transform do
|
|||
|
||||
apply_transform(shape, fun, "rotate-#{degrees}-around-center")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rotate a shape around an origin point.
|
||||
|
||||
## Example
|
||||
|
||||
iex> Vivid.Box.init(Vivid.Point.init(10,10), Vivid.Point.init(20,20))
|
||||
...> |> Vivid.Transform.rotate(45, Vivid.Point.init(5,5))
|
||||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{12.071067811865476, 13.535533905932738}>, #Vivid.Point<{5.000000000000002, 15.606601717798215}>, #Vivid.Point<{-2.0710678118654737, 10.606601717798215}>, #Vivid.Point<{5.0, 8.535533905932738}>]>
|
||||
"""
|
||||
@spec rotate(shape_or_transform, degrees, Point.t) :: Transform.t
|
||||
def rotate(shape, degrees, %Point{x: x, y: y}=origin) do
|
||||
radians = degrees_to_radians(degrees)
|
||||
fun = fn _shape ->
|
||||
|
@ -120,6 +140,7 @@ defmodule Vivid.Transform do
|
|||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{11.0, 1.0}>, #Vivid.Point<{11.0, 11.0}>, #Vivid.Point<{1.0, 11.0}>, #Vivid.Point<{1.0, 1.0}>]>
|
||||
"""
|
||||
@spec center(shape_or_transform, Shape.t) :: Transform.t
|
||||
def center(shape, bounds) do
|
||||
bounds = Bounds.bounds(bounds)
|
||||
bounds_width = Bounds.width(bounds)
|
||||
|
@ -144,6 +165,7 @@ defmodule Vivid.Transform do
|
|||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{40.0, 0.0}>, #Vivid.Point<{40.0, 80.0}>, #Vivid.Point<{0.0, 80.0}>, #Vivid.Point<{0.0, 0.0}>]>
|
||||
"""
|
||||
@spec stretch(shape_or_transform, Shape.t) :: Transform.t
|
||||
def stretch(shape, bounds) do
|
||||
bounds = Bounds.bounds(bounds)
|
||||
bounds_min = Bounds.min(bounds)
|
||||
|
@ -179,6 +201,7 @@ defmodule Vivid.Transform do
|
|||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{40.0, 0.0}>, #Vivid.Point<{40.0, 40.0}>, #Vivid.Point<{0.0, 40.0}>, #Vivid.Point<{0.0, 0.0}>]>
|
||||
"""
|
||||
@spec fill(shape_or_transform, Shape.t) :: Transform.t
|
||||
def fill(shape, bounds) do
|
||||
bounds = Bounds.bounds(bounds)
|
||||
bounds_min = Bounds.min(bounds)
|
||||
|
@ -216,6 +239,7 @@ defmodule Vivid.Transform do
|
|||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{80.0, 0.0}>, #Vivid.Point<{80.0, 80.0}>, #Vivid.Point<{0.0, 80.0}>, #Vivid.Point<{0.0, 0.0}>]>
|
||||
"""
|
||||
@spec overflow(shape_or_transform, Shape.t) :: Transform.t
|
||||
def overflow(shape, bounds) do
|
||||
bounds = Bounds.bounds(bounds)
|
||||
bounds_min = Bounds.min(bounds)
|
||||
|
@ -246,15 +270,16 @@ defmodule Vivid.Transform do
|
|||
Create an arbitrary transformation.
|
||||
|
||||
Takes a shape and a function which is called with a shape argument (not necessarily the shape
|
||||
passed-in, depending on where this transformation is in the transformation pipeline.
|
||||
passed-in, depending on where this transformation is in the transformation pipeline).
|
||||
|
||||
The function must return another function which takes and manipulates a point.
|
||||
|
||||
## Example
|
||||
|
||||
The example below translates a point right by half it's width.
|
||||
|
||||
iex> Vivid.Box.init(Vivid.Point.init(10,10), Vivid.Point.init(20,20))
|
||||
...> |> Vivid.Transform.transform(fn shape ->
|
||||
...> # Translate a point right by half it's width
|
||||
...> width = Vivid.Bounds.width(shape)
|
||||
...> fn point ->
|
||||
...> x = point |> Vivid.Point.x
|
||||
|
@ -266,11 +291,13 @@ defmodule Vivid.Transform do
|
|||
...> |> Vivid.Transform.apply
|
||||
#Vivid.Polygon<[#Vivid.Point<{25, 10}>, #Vivid.Point<{25, 20}>, #Vivid.Point<{15, 20}>, #Vivid.Point<{15, 10}>]>
|
||||
"""
|
||||
@spec transform(shape_or_transform, function) :: Transform.t
|
||||
def transform(shape, fun), do: apply_transform(shape, fun, inspect(fun))
|
||||
|
||||
@doc """
|
||||
Apply a transformation pipeline returning the modified shape.
|
||||
"""
|
||||
@spec apply(Transform.t) :: Shape.t
|
||||
def apply(%Transform{operations: operations, shape: shape}) do
|
||||
operations
|
||||
|> Enum.reverse
|
||||
|
|
|
@ -5,19 +5,30 @@ defmodule Vivid.Transform.Point do
|
|||
@moduledoc """
|
||||
Standard transformations which can be applied to points without
|
||||
knowing the details of the geometry.
|
||||
|
||||
Used extensively by `Transform`, however you can use these functions
|
||||
as input to the `Transformable` protocol, should you require.
|
||||
"""
|
||||
|
||||
@type degrees :: number
|
||||
@type radians :: number
|
||||
|
||||
@doc """
|
||||
Translate a point (ie move it) by adding `x` and `y` to it's coordinates.
|
||||
"""
|
||||
@spec translate(Point.t, number, number) :: Point.t
|
||||
def translate(%Point{x: x0, y: y0}, x, y), do: Point.init(x0 + x, y0 + y)
|
||||
|
||||
@doc """
|
||||
Scale a point (ie move it) by multiplying it's distance from the origin point by `x_factor` and `y_factor`.
|
||||
The default origin point is `{0, 0}`
|
||||
Scale a point (ie move it) by multiplying it's distance from the `0`, `0` point by `x_factor` and `y_factor`.
|
||||
"""
|
||||
@spec scale(Point, number, number) :: Point.t
|
||||
def scale(%Point{}=point, x_factor, y_factor), do: scale(point, x_factor, y_factor, Point.init(0,0))
|
||||
def scale(%Point{x: x, y: y}, x_factor, y_factor, %Point{x: xo, y: yo}) do
|
||||
|
||||
@doc """
|
||||
Scale a point (ie move it) by multiplying it's distance from the origin point by `x_factor` and `y_factor`.
|
||||
"""
|
||||
def scale(%Point{x: x, y: y}=_point, x_factor, y_factor, %Point{x: xo, y: yo}=_origin) do
|
||||
x = (x - xo) * x_factor + xo
|
||||
y = (y - yo) * y_factor + yo
|
||||
Point.init(x, y)
|
||||
|
@ -26,11 +37,13 @@ defmodule Vivid.Transform.Point do
|
|||
@doc """
|
||||
Rotate a point `degrees` around an origin point.
|
||||
"""
|
||||
@spec rotate(Point.t, Point.t, degrees) :: Point.t
|
||||
def rotate(point, origin, degrees), do: rotate_radians(point, origin, degrees_to_radians(degrees))
|
||||
|
||||
@doc """
|
||||
Rotate a point `radians` around an origin point.
|
||||
"""
|
||||
@spec rotate_radians(Point.t, Point.t, radians) :: Point.t
|
||||
def rotate_radians(%Point{x: x0, y: y0}, %Point{x: x1, y: y1}, radians) do
|
||||
x = x0 - x1
|
||||
y = y0 - y1
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
defprotocol Vivid.Transformable do
|
||||
def transform(shape, fun)
|
||||
alias Vivid.Shape
|
||||
@moduledoc """
|
||||
This protocol is used to apply *point* transformations to a shape.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Transform all of a shape's points using `fun`.
|
||||
"""
|
||||
@spec transform(Shape.t, function) :: Shape.t
|
||||
def transform(shape, fun)
|
||||
end
|
4
test/vivid/box_test.exs
Normal file
4
test/vivid/box_test.exs
Normal file
|
@ -0,0 +1,4 @@
|
|||
defmodule Vivid.BoxTest do
|
||||
use ExUnit.Case
|
||||
doctest Vivid.Box
|
||||
end
|
4
test/vivid/buffer_test.exs
Normal file
4
test/vivid/buffer_test.exs
Normal file
|
@ -0,0 +1,4 @@
|
|||
defmodule Vivid.BufferTest do
|
||||
use ExUnit.Case
|
||||
doctest Vivid.Buffer
|
||||
end
|
4
test/vivid/font_test.exs
Normal file
4
test/vivid/font_test.exs
Normal file
|
@ -0,0 +1,4 @@
|
|||
defmodule Vivid.FontTest do
|
||||
use ExUnit.Case
|
||||
doctest Vivid.Font
|
||||
end
|
4
test/vivid/math_test.exs
Normal file
4
test/vivid/math_test.exs
Normal file
|
@ -0,0 +1,4 @@
|
|||
defmodule Vivid.MathTest do
|
||||
use ExUnit.Case
|
||||
doctest Vivid.Math
|
||||
end
|
Loading…
Reference in a new issue