defmodule Isotope.Noise do @moduledoc """ Create noise generators, sample individual points, and generate noise maps. ## Creating a Noise Generator All noise operations require a noise reference, created with `new/0` or `new/1`: {:ok, noise} = Isotope.Noise.new() {:ok, noise} = Isotope.Noise.new(%Isotope.Options{ seed: 42, noise_type: :perlin_fractal, frequency: 0.02, fractal_options: %Isotope.Options.Fractal{octaves: 6, gain: 0.5} }) ## Generating Noise Maps `noise_map/2` generates a full map starting at the origin. `chunk/4` generates a region starting at an arbitrary coordinate, useful for tiling or infinite worlds: nm = Isotope.Noise.noise_map(noise, {256, 256}) chunk = Isotope.Noise.chunk(noise, {512, 512}, 256, 256) Both return an `%Isotope.NoiseMap{}` struct. See `Isotope.NoiseMap` for accessors. ## Sampling Individual Points Use `get_noise/2` with a 2D or 3D coordinate tuple: value = Isotope.Noise.get_noise(noise, {1.5, 2.5}) value = Isotope.Noise.get_noise(noise, {1.5, 2.5, 3.5}) Coordinates are passed directly to the underlying noise algorithm. Use the `frequency` option to control the scale of the noise. ## Supported Noise Types `:perlin`, `:perlin_fractal`, `:simplex`, `:simplex_fractal`, `:value`, `:value_fractal`, `:cubic`, `:cubic_fractal`, `:cellular`, `:white` """ @noise_types [ :perlin, :perlin_fractal, :simplex, :simplex_fractal, :value, :value_fractal, :cubic, :cubic_fractal, :cellular, :white ] alias Isotope.{NIF, NoiseMap, 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 containing packed f32 noise values with dimensions. """ @type noisemap() :: NoiseMap.t() @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.NoiseMap{width: 100, height: 100} = 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 {data, w, h} = NIF.chunk(noise, start_x, start_y, width, height) %NoiseMap{data: data, width: w, height: h} end @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> is_float(Isotope.Noise.get_noise(noise, {10.0, 10.0})) true iex> {:ok, noise} = Isotope.Noise.new() iex> is_float(Isotope.Noise.get_noise(noise, {10.0, 10.0, 10.0})) true """ @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.NoiseMap{width: 20, height: 20} = 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 {data, width, height} = NIF.noise_map(noise, w, h) %NoiseMap{data: data, width: width, height: height} end end