-- Copyright (c) 2011, Colin Hill

-- | Implementation of ridged multi-fractal noise.
--
-- Example of use:
--
-- @
--main = putStrLn (\"Noise value at (1, 2, 3): \" ++ show x)
--    where seed        = 1
--          octaves     = 5
--          scale       = 0.005
--          frequency   = 1
--          lacunarity  = 2
--          ridgedNoise = ridged seed octaves scale frequency lacunarity
--          x           = noiseValue ridgedNoise (1, 2, 3)
-- @
module Numeric.Noise.Ridged (
    Ridged,
    ridged,
    noiseValue
) where

import Numeric.Noise

import Data.Bits ((.&.))
import Data.Vector.Unboxed (Vector, fromList, (!))

-- | A ridged multi-fractal noise function.
data Ridged = Ridged Seed Int Double Double Double (Vector Double)

-- | Constructs a ridged multi-fractal noise function given a seed, number of octaves, scale, 
-- frequency, and lacunarity.
ridged :: Seed -> Int -> Double -> Double -> Double -> Ridged
ridged seed octs scale freq lac = ridgedNoise
    where specWeights = computeSpecWeights octs lac
          ridgedNoise = Ridged seed octs scale freq lac specWeights

instance Noise Ridged where
    noiseValue ridgedNoise xyz = clamp noise (-1) 1
        where Ridged _ octs scale freq _ _ = ridgedNoise
              xyz'  = pmap (* (scale * freq)) xyz
              noise = ridgedNoiseValue ridgedNoise octs 1 xyz' * 1.25 - 1

-- | Computes the noise value for a ridged multi-fractal noise function given the octave number, 
-- the weight, and the point.
ridgedNoiseValue :: Ridged -> Int -> Double -> Point -> Double
ridgedNoiseValue _           0   _      _   = 0
ridgedNoiseValue ridgedNoise oct weight xyz = noise + noise'
    where Ridged seed octs _ _ lac specWeights = ridgedNoise
          oct'    = oct - 1
          xyz'    = pmap (* lac) xyz
          seed'   = (seed + (octs - oct)) .&. 0x7fffffff
          signal  = (offset - abs (coherentNoise seed' xyz)) * weight * weight
          weight' = clamp (signal * gain) 0 1
          noise   = signal * (specWeights ! (octs - oct))
          noise'  = ridgedNoiseValue ridgedNoise oct' weight' xyz'
          gain    = 2
          offset  = 1

-- | Computes the spectral weight for each oct given the number of octs and the lac.
computeSpecWeights :: Int -> Double -> Vector Double
computeSpecWeights octs lac = fromList (computeSpecWeights' octs lac 1)

-- | Helper for 'computeSpecWeights'.
computeSpecWeights' :: Int -> Double -> Double -> [Double]
computeSpecWeights' 0   _   _    = []
computeSpecWeights' oct lac freq = weight : weights
    where freq'   = freq * lac
          oct'    = oct - 1
          weight  = freq ** (-1)
          weights = computeSpecWeights' oct' lac freq'