raytrace-0.1.0.0: src/Graphics/Ray/Texture.hs
{-# LANGUAGE FlexibleContexts #-}
module Graphics.Ray.Texture
( Texture(Texture)
, constantTexture, solidTexture, uvTexture, imageTexture, checkerTexture, noiseTexture, marbleTexture
) where
import Graphics.Ray.Core
import Graphics.Ray.Noise
import Linear (V2(V2), V3, (^*), (*^), dot)
import qualified Data.Massiv.Array as A
import Data.Massiv.Array (Ix2((:.)), (!))
import Data.Bits ((.&.))
newtype Texture = Texture (Point3 -> V2 Double -> Color)
-- | Texture that is the same color everywhere.
constantTexture :: Color -> Texture
constantTexture color = Texture (\_ _ -> color)
-- | Texture assigning a color to each point in 3D space.
solidTexture :: (Point3 -> Color) -> Texture
solidTexture f = Texture (\p _ -> f p)
-- | Texture whose color depends on two parameters (u, v) that vary across a surface.
uvTexture :: (V2 Double -> Color) -> Texture
uvTexture f = Texture (const f)
-- | Convert an image into a texture. (u, v) = (0, 0) maps to the bottom left of the image,
-- (u, v) = (1, 1) maps to the top right, and coordinates outside of this range wrap around.
imageTexture :: (A.Manifest r Color) => A.Matrix r Color -> Texture
imageTexture image = let
A.Sz (h :. w) = A.size image
w' = fromIntegral w
h' = fromIntegral h
in
uvTexture $ \(V2 u v) -> let
i = floor (u * w') `mod` w
j = floor ((1 - v) * h') `mod` h
in
image ! (j :. i)
-- | UV texture with two colors alternating in a checkerboard pattern. The first two arguments
-- are the dimensions of the checkerboard.
checkerTexture :: Int -> Int -> Color -> Color -> Texture
checkerTexture n_u n_v c0 c1 = let
n_u' = fromIntegral n_u
n_v' = fromIntegral n_v
in
uvTexture $ \(V2 u v) -> let
i = floor (u * n_u')
j = floor (v * n_v')
in if (i + j) .&. 1 == (0 :: Int) then c0 else c1
-- | Perlin noise texture.
noiseTexture
:: Int -- ^ Number of layers of noise (see 'fractalNoise')
-> Double -- ^ Noise frequency
-> V3 Double -- ^ Shift applied before calling noise function
-> Color -- ^ Color 1
-> Color -- ^ Color 2
-> Texture
noiseTexture k freq shift color0 color1 = let
scale = 0.5 / 0.8
getNoise p = fractalNoise k (p ^* freq + shift) * scale + 0.5
diff = color1 - color0
in solidTexture $ \p -> color0 + diff ^* getNoise p
-- | Texture with noisy black and white stripes, resulting in a marble-like appearance.
marbleTexture
:: Vec3 -- ^ Direction of stripes
-> Double -- ^ Frequency
-> V3 Double -- ^ Shift applied before calling noise function
-> Texture
marbleTexture dir freq shift =
solidTexture $ \p -> let
sinArg = freq * dot dir p
noise = 10 * turbulence 7 (0.25 * freq *^ p + shift)
in 1 ^* (0.5 + 0.5 * sin (sinArg + noise))