lib/noise.ex

defmodule Isotope.Noise do
  @moduledoc """
  Provide functions to create and work with different types of noises.
  """

  @noise_types [
    :perlin,
    :perlin_fractal,
    :simplex,
    :simplex_fractal,
    :value,
    :value_fractal,
    :cubic,
    :cubic_fractal,
    :cellular,
    :white
  ]

  alias Isotope.{NIF, Options}

  @typedoc """
  A reference to the noise generator. This is
  needed for most of the library functions.
  """
  @type noise_ref() :: reference()

  @typedoc """
  A noise map, represented by a list
  containing lists of floats (the noise values).
  """
  @type noisemap() :: [[float()]]

  @typedoc """
  A coordinate `{x, y}` in a cartesian plane.
  """
  @type coord() :: {integer(), integer()}

  @typedoc """
  2-element tuple containg x and y values as floats.
  """
  @type point2d() :: {float(), float()}

  @typedoc """
  3-element tuple containg x, y and z values as floats.
  """
  @type point3d() :: {float(), float(), float()}

  @typedoc """
  A tuple containing width and height
  """
  @type size() :: {non_neg_integer(), non_neg_integer()}

  @typedoc """
  Options available when initializing the noise.
  """
  @type options() :: Options.t()

  @doc """
  Returns a new noise reference using the default options.

      iex> {:ok, _ref} = Isotope.Noise.new()
  """
  @spec new(options()) :: {:ok, noise_ref()} | {:error, :unsupported_noise}
  def new(), do: NIF.new(%Options{})

  @doc """
  Returns a new noise reference using the provided `options`.

      iex> {:ok, _ref} = Isotope.Noise.new(%Isotope.Options{seed: 100})

      iex> {:error, :unsupported_noise} = Isotope.Noise.new(%Isotope.Options{noise_type: :foobar})
  """
  def new(options) when options.noise_type not in @noise_types,
    do: {:error, :unsupported_noise}

  def new(options), do: NIF.new(options)

  @doc """
  Returns a 2D noise map from `start_point` which has `width` and `height`

      iex> {:ok, noise} = Isotope.Noise.new(%Isotope.Options{seed: 100})
      iex> Isotope.Noise.chunk(noise, {0, 0}, 100, 100)
  """
  @spec chunk(noise_ref(), coord(), non_neg_integer(), non_neg_integer()) ::
          noisemap()
  def chunk(noise, start_point, width, height)

  def chunk(noise, {start_x, start_y}, width, height),
    do: NIF.chunk(noise, start_x, start_y, width, height)

  @doc """
  Returns the 2D or 3D noise value depending on `axes`.
  If `axes` is a 2-float tuple, it will return the 2D noise value for the point.
  If `axes` is a 3-float tuple, it will return the 3D noise value for the point.

      iex> {:ok, noise} = Isotope.Noise.new()
      iex> Isotope.Noise.get_noise(noise, {10.0, 10.0})
      -0.6350845098495483

      iex> {:ok, noise} = Isotope.Noise.new()
      iex> Isotope.Noise.get_noise(noise, {10.0, 10.0, 10.0})
      -0.1322503685951233
  """
  @spec get_noise(noise_ref(), point2d() | point3d()) :: float()
  def get_noise(noise, axes)
  def get_noise(noise, {x, y}), do: NIF.get_noise(noise, x, y)
  def get_noise(noise, {x, y, z}), do: NIF.get_noise3d(noise, x, y, z)

  @doc """
  Generates a 2D noise map of `size` and returns it.

      iex> {:ok, noise} = Isotope.Noise.new()
      iex> Isotope.Noise.noise_map(noise, {20, 20})
  """
  @spec noise_map(reference(), size()) :: noisemap()
  def noise_map(noise, size)
  def noise_map(noise, {w, h}), do: NIF.noise_map(noise, w, h)
end