Skip to content

Commit

Permalink
Merge pull request #12 from amclain/animation-refactor
Browse files Browse the repository at this point in the history
Animation refactor
  • Loading branch information
amclain authored Apr 22, 2020
2 parents 0de830e + 07654b6 commit a68721d
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 181 deletions.
96 changes: 96 additions & 0 deletions lib/xebow/animation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
defmodule Xebow.Animation do
alias Xebow.RGBMatrix

@callback init_state(pixels :: list(RGBMatrix.pixel())) :: t
@callback next_state(animation :: t) :: t

@type t :: %__MODULE__{
type: type,
tick: non_neg_integer,
speed: non_neg_integer,
delay_ms: non_neg_integer,
pixels: list(RGBMatrix.pixel()),
pixel_colors: list(RGBMatrix.pixel_color())
}
defstruct [:type, :tick, :speed, :delay_ms, :pixels, :pixel_colors]

# Helpers for implementing animations.
defmacro __using__(_) do
quote do
alias Xebow.Animation

@behaviour Animation

@impl true
def init_state(pixels) do
init_state_from_defaults(__MODULE__, pixels)
end

# Increment the animation state to the next tick.
@spec do_tick(animation :: Animation.t()) :: Animation.t()
defp do_tick(animation) do
%Animation{animation | tick: animation.tick + 1}
end

# Initialize an `Animation` struct with default values.
# Defaults can be overridden by passing the corresponding keyword as `opts`.
@spec init_state_from_defaults(
animation_type :: Animation.type(),
pixels :: list(RGBMatrix.pixel()),
opts :: list(keyword)
) :: Animation.t()
defp init_state_from_defaults(animation_type, pixels, opts \\ []) do
%Animation{
type: animation_type,
tick: opts[:tick] || 0,
speed: opts[:speed] || 100,
delay_ms: opts[:delay_ms] || 17,
pixels: pixels,
pixel_colors: opts[:pixel_colors] || init_pixel_colors(pixels)
}
end

# Initialize a list of default pixel colors.
# The default sets all pixels to be turned off ("black").
@spec init_pixel_colors(pixels :: list(RGBMatrix.pixel())) :: list(RGBMatrix.pixel_color())
defp init_pixel_colors(pixels) do
Enum.map(pixels, fn _pixel -> Chameleon.HSV.new(0, 0, 0) end)
end

defoverridable init_state: 1
end
end

@type type ::
__MODULE__.CycleAll
| __MODULE__.CycleLeftToRight
| __MODULE__.Pinwheel

@doc """
Returns a list of the available types of animations.
"""
@spec types :: list(type)
def types do
[
__MODULE__.CycleAll,
__MODULE__.CycleLeftToRight,
__MODULE__.Pinwheel
]
end

@doc """
Returns an animation set to its initial state.
"""
@spec init_state(animation_type :: type, pixels :: list(RGBMatrix.pixel())) :: t
def init_state(animation_type, pixels) do
animation_type.init_state(pixels)
end

@doc """
Returns the next state of an animation based on its current state.
"""
@spec next_state(animation :: t) :: t
def next_state(animation) do
animation.type.next_state(animation)
end
end
27 changes: 27 additions & 0 deletions lib/xebow/animation/cycle_all.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule Xebow.Animation.CycleAll do
@moduledoc """
Cycles hue of all keys.
"""

alias Chameleon.HSV

alias Xebow.Animation

import Xebow.Utils, only: [mod: 2]

use Animation

@impl true
def next_state(animation) do
%Animation{tick: tick, speed: speed, pixels: pixels} = animation
time = div(tick * speed, 100)

hue = mod(time, 360)
color = HSV.new(hue, 100, 100)

pixel_colors = Enum.map(pixels, fn {_x, _y} -> color end)

%Animation{animation | pixel_colors: pixel_colors}
|> do_tick()
end
end
28 changes: 28 additions & 0 deletions lib/xebow/animation/cycle_left_to_right.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule Xebow.Animation.CycleLeftToRight do
@moduledoc """
Cycles hue left to right.
"""

alias Chameleon.HSV

alias Xebow.Animation

import Xebow.Utils, only: [mod: 2]

use Animation

@impl true
def next_state(animation) do
%Animation{tick: tick, speed: speed, pixels: pixels} = animation
time = div(tick * speed, 100)

pixel_colors =
for {x, _y} <- pixels do
hue = mod(x * 10 - time, 360)
HSV.new(hue, 100, 100)
end

%Animation{animation | pixel_colors: pixel_colors}
|> do_tick()
end
end
43 changes: 43 additions & 0 deletions lib/xebow/animation/pinwheel.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule Xebow.Animation.Pinwheel do
@moduledoc """
Cycles hue in a pinwheel pattern.
"""

alias Chameleon.HSV

alias Xebow.Animation

import Xebow.Utils, only: [mod: 2]

use Animation

@center %{
x: 1,
y: 1.5
}

@impl true
def next_state(animation) do
%Animation{tick: tick, speed: speed, pixels: pixels} = animation
time = div(tick * speed, 100)

pixel_colors =
for {x, y} <- pixels do
dx = x - @center.x
dy = y - @center.y

hue = mod(atan2_8(dy, dx) + time, 360)

HSV.new(hue, 100, 100)
end

%Animation{animation | pixel_colors: pixel_colors}
|> do_tick()
end

defp atan2_8(x, y) do
atan = :math.atan2(x, y)

trunc((atan + :math.pi()) * 359 / (2 * :math.pi()))
end
end
70 changes: 35 additions & 35 deletions lib/xebow/rgb_matrix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ defmodule Xebow.RGBMatrix do
use GenServer

alias Circuits.SPI
alias Xebow.RGBMatrix.Animations
alias Xebow.Animation

import Xebow.Utils, only: [mod: 2]

defmodule State do
@fields [:spidev, :animation, :animation_state]
@enforce_keys @fields

defstruct @fields
defstruct [:spidev, :animation]
end

@type pixels :: list({non_neg_integer, non_neg_integer})
@type colors :: list(any)
@type any_color_model ::
Chameleon.Color.RGB.t()
| Chameleon.Color.CMYK.t()
| Chameleon.Color.Hex.t()
| Chameleon.Color.HSL.t()
| Chameleon.Color.HSV.t()
| Chameleon.Color.Keyword.t()
| Chameleon.Color.Pantone.t()

@type pixel :: {non_neg_integer, non_neg_integer}
@type pixel_color :: any_color_model

@spi_device "spidev0.0"
@spi_speed_hz 4_000_000
Expand Down Expand Up @@ -67,34 +73,28 @@ defmodule Xebow.RGBMatrix do

send(self(), :get_next_state)

[initial_animation | _] = Animations.list()
[initial_animation_type | _] = Animation.types()

state =
%State{spidev: spidev}
|> set_animation(initial_animation_type)

{:ok,
%State{
spidev: spidev,
animation: initial_animation,
animation_state: initial_animation.init_state()
}}
{:ok, state}
end

defp set_animation(state, animation) do
%{
state
| animation: animation,
animation_state: animation.init_state()
}
defp set_animation(state, animation_type) do
%State{state | animation: Animation.init_state(animation_type, @pixels)}
end

@impl true
def handle_info(:get_next_state, state) do
{colors, delay, new_animation_state} =
state.animation.next_state(@pixels, state.animation_state)
new_animation_state = Animation.next_state(state.animation)

paint(state.spidev, colors)
paint(state.spidev, new_animation_state.pixel_colors)

Process.send_after(self(), :get_next_state, delay)
Process.send_after(self(), :get_next_state, new_animation_state.delay_ms)

{:noreply, %{state | animation_state: new_animation_state}}
{:noreply, %State{state | animation: new_animation_state}}
end

defp paint(spidev, colors) do
Expand Down Expand Up @@ -123,22 +123,22 @@ defmodule Xebow.RGBMatrix do
end

def handle_cast(:next_animation, state) do
animations = Animations.list()
num = Enum.count(animations)
current = Enum.find_index(animations, &(&1 == state.animation))
animation_types = Animation.types()
num = Enum.count(animation_types)
current = Enum.find_index(animation_types, &(&1 == state.animation.type))
next = mod(current + 1, num)
animation = Enum.at(animations, next)
animation_type = Enum.at(animation_types, next)

{:noreply, set_animation(state, animation)}
{:noreply, set_animation(state, animation_type)}
end

def handle_cast(:previous_animation, state) do
animations = Animations.list()
num = Enum.count(animations)
current = Enum.find_index(animations, &(&1 == state.animation))
animation_types = Animation.types()
num = Enum.count(animation_types)
current = Enum.find_index(animation_types, &(&1 == state.animation.type))
previous = mod(current - 1, num)
animation = Enum.at(animations, previous)
animation_type = Enum.at(animation_types, previous)

{:noreply, set_animation(state, animation)}
{:noreply, set_animation(state, animation_type)}
end
end
9 changes: 0 additions & 9 deletions lib/xebow/rgb_matrix/animation.ex

This file was deleted.

11 changes: 0 additions & 11 deletions lib/xebow/rgb_matrix/animations.ex

This file was deleted.

36 changes: 0 additions & 36 deletions lib/xebow/rgb_matrix/animations/cycle_all.ex

This file was deleted.

Loading

0 comments on commit a68721d

Please sign in to comment.