rsagl-0.6.0.1: RSAGL/Modeling/Material.hs
{-# OPTIONS_GHC -fglasgow-exts #-}
-- | RSAGL.Material handles properties of surfaces such as color, shininess, and transparency
-- including procedural textures but not including anything touching the normal vector, such
-- as bumpiness. Materials are handled using layers.
--
module RSAGL.Modeling.Material
(MaterialLayer,MaterialSurface,Material,materialIsEmpty,
toLayers,materialLayerSurface,materialLayerRelevant,materialComplexity,materialLayerToOpenGLWrapper,
isOpaqueLayer,
diffuseLayer,RSAGL.Modeling.Material.specularLayer,transparentLayer,emissiveLayer,filteringLayer)
where
import Data.Maybe
import Data.Monoid
import Control.Applicative
import RSAGL.Color
import RSAGL.Math.Curve
import RSAGL.Auxiliary.ApplicativeWrapper
import Control.DeepSeq
import Control.Parallel.Strategies
import Graphics.Rendering.OpenGL.GL hiding (RGB,RGBA,Alpha)
import RSAGL.Math.Types
import RSAGL.Math.AbstractVector
-- | A 'MaterialSurface' is parameterized either on RGB or RGBA, depending
-- on whether or not the 'MaterialLayer' is capable of transparency.
-- 'MaterialSurface's that are only one color (as opposed to procedural textures)
-- can be described using 'pure', for example "pure red".
--
type MaterialSurface a = ApplicativeWrapper Surface a
-- | A 'MaterialLayer' is a layer of material some material quality (diffuse, transparent, emissive, or specular highlight).
-- 'MaterialLayers' are rendered one on top of another to create layered effects.
data MaterialLayer =
-- | A simple colored material.
DiffuseLayer (MaterialSurface RGB)
-- | A transparent colored material.
| TransparentLayer (MaterialSurface RGBA)
-- | A glowing material.
| EmissiveLayer (MaterialSurface RGB)
-- | A shiny material with specular highlight. Includes the specular exponent.
| SpecularLayer (MaterialSurface RGB) GLfloat
-- | A compound layer of diffuse, pure specular, and pure emissive layers. This is a common use case, and therefore optimized into one layer.
| CompoundLayer (MaterialSurface RGB) RGB RGB GLfloat
-- | A layer that filters (multiplies) light from behind, but doesn't reflect or glow at all.
| FilterLayer (MaterialSurface RGB)
instance NFData MaterialLayer where
rnf (DiffuseLayer msrgb) = rnf msrgb
rnf (TransparentLayer msrgba) = rnf msrgba
rnf (EmissiveLayer msrgb) = rnf msrgb
rnf (SpecularLayer msrgb shininess) = shininess `seq` rnf msrgb
rnf (CompoundLayer msrgb spec emis shininess) = shininess `seq` rnf (msrgb,spec,emis)
rnf (FilterLayer msrgb) = rnf msrgb
-- | A stack of 'MaterialLayer's. 'Material' is smart about compressing multiple layers into the least of number of equivalent layers.
data Material = Material [MaterialLayer]
-- | Split open a Material into its component layers.
toLayers :: Material -> [MaterialLayer]
toLayers (Material layers) = layers
-- | Wherever possible, combine material layers into one material layer.
-- For example, two emissive layers can be directly added together.
-- See 'combine2Layers'.
combineLayers :: [MaterialLayer] -> [MaterialLayer]
combineLayers (x1:x2:xs) | Just x <- combine2Layers x1 x2 =
let combine_here = combineLayers $ x : xs
combine_rest = combineLayers $ x2 : xs
in case length combine_rest < length combine_here of
True -> combineLayers $ x1 : combine_rest
False -> combine_here
combineLayers (x:xs) = x : combineLayers xs
combineLayers xs = xs
-- | Tries to combine exactly two layers, or answers Nothing if the layers can't be combined.
combine2Layers :: MaterialLayer -> MaterialLayer -> Maybe MaterialLayer
-- diffuse + pure specular
combine2Layers (DiffuseLayer msrgb) (SpecularLayer specular_rgb shininess) | isPure specular_rgb =
Just $ CompoundLayer msrgb (fromJust $ fromPure $ specular_rgb) (RGB 0 0 0) shininess
-- diffuse + pure emissive
combine2Layers (DiffuseLayer msrgb) (EmissiveLayer emissive_rgb) | isPure emissive_rgb =
Just $ CompoundLayer msrgb (RGB 0 0 0) (fromJust $ fromPure $ emissive_rgb) 0
-- emissive + emissive
combine2Layers (EmissiveLayer x) (EmissiveLayer y) = Just $ EmissiveLayer $ add <$> x <*> y
-- compound + pure emissive
combine2Layers (CompoundLayer msrgb specular_rgb emissive_rgb1 shininess) (EmissiveLayer emissive_rgb2) | isPure emissive_rgb2 =
Just $ CompoundLayer msrgb specular_rgb (add emissive_rgb1 (fromJust $ fromPure $ emissive_rgb2)) shininess
-- compound + pure specular
combine2Layers (CompoundLayer msrgb (RGB 0 0 0) emissive_rgb 0) (SpecularLayer specular_rgb shininess) | isPure specular_rgb =
Just $ CompoundLayer msrgb (fromJust $ fromPure $ specular_rgb) emissive_rgb shininess
-- filter + filter
combine2Layers (FilterLayer x) (FilterLayer y) =
Just $ FilterLayer $ filterRGB <$> x <*> y
combine2Layers _ _ = Nothing
instance Monoid Material where
mempty = Material []
mappend (Material xs) (Material ys) = Material $ combineLayers $ (\zs -> if null (snd zs) then fst zs else snd zs) $
span (not . isOpaqueLayer) $ xs ++ ys
materialIsEmpty :: Material -> Bool
materialIsEmpty (Material xs) = null xs
-- | A measure of how much color variation should be expected between vertices of a model rendered with this material.
-- Materials using procedural textures are weighted more heavily than others, and specular textures are weighted very
-- heavily. Materials with constant properties, such as pure emissive and black diffuse layers, have a complexity of zero.
-- This is a heuristic used to assign more vertices to more complex materials.
--
materialLayerComplexity :: MaterialLayer -> Integer
materialLayerComplexity layer | fromPure (materialLayerRelevant layer) == Just False = 0
materialLayerComplexity (DiffuseLayer ms) | fromPure ms == Just (RGB 0 0 0) = 0
materialLayerComplexity (DiffuseLayer ms) | isPure ms = 1
materialLayerComplexity (DiffuseLayer {}) = 2
materialLayerComplexity (TransparentLayer ms) | isPure ms = 1
materialLayerComplexity (TransparentLayer {}) = 2
materialLayerComplexity (EmissiveLayer ms) | isPure ms = 0
materialLayerComplexity (EmissiveLayer {}) = 2
materialLayerComplexity (SpecularLayer ms _) | isPure ms = 3
materialLayerComplexity (SpecularLayer {}) = 4
materialLayerComplexity (CompoundLayer {}) = 3
materialLayerComplexity (FilterLayer ms) | isPure ms = 0
materialLayerComplexity (FilterLayer {}) = 2
-- | Answers a complexity heuristic for a 'Material'. Result is a small integer greater than or equal to zero.
materialComplexity :: Material -> Integer
materialComplexity (Material []) = 0
materialComplexity (Material layers) = maximum $ map materialLayerComplexity layers
-- | True if the 'MaterialLayer' is completely opaque. A layer under an opaque layer is not visible.
isOpaqueLayer :: MaterialLayer -> Bool
isOpaqueLayer (DiffuseLayer _) = True
isOpaqueLayer (TransparentLayer ms) | fmap alpha_alpha (fromPure ms) == Just 1.0 = True
isOpaqueLayer (CompoundLayer _ _ _ _) = True
isOpaqueLayer _ = False
-- | True if the color is not black. Black emissive materials contribue nothing to the color of a model,
-- and can therefore be eleminated from a model.
--
isEmissiveRelevant :: RGB -> Bool
isEmissiveRelevant (RGB r g b) | r <= 0 && g <= 0 && b <= 0 = False
isEmissiveRelevant _ = True
-- | True is the color is not white. White filter materials don't filter any light, and can therefore be
-- eleminated from a model.
isFilterRelevant :: RGB -> Bool
isFilterRelevant (RGB r g b) | r >= 1 && g >= 1 && b >= 1 = False
isFilterRelevant _ = True
-- | True if the color is not perfectly transparent. Perfectly transparent materials are invisible, and can therefore
-- be eleminated from a model.
isTransparentRelevant :: RGBA -> Bool
isTransparentRelevant (Alpha x _) | x < 0.01 = False
isTransparentRelevant _ = True
-- | Get the color information for a 'MaterialLayer'.
materialLayerSurface :: MaterialLayer -> MaterialSurface RGBA
materialLayerSurface (DiffuseLayer msrgb) = fmap transformColor msrgb
materialLayerSurface (TransparentLayer msrgba) = msrgba
materialLayerSurface (EmissiveLayer msrgb) = fmap transformColor msrgb
materialLayerSurface (SpecularLayer msrgb _) = fmap transformColor msrgb
materialLayerSurface (CompoundLayer msrgb _ _ _) = fmap transformColor msrgb
materialLayerSurface (FilterLayer msrgb) = fmap transformColor msrgb
-- | Get a relevance layer for a surface. Purely irrelevant materials can be removed without changing the
-- appearance of a model. Irrelevant triangles can also be selectively culled from a model.
materialLayerRelevant :: MaterialLayer -> MaterialSurface Bool
materialLayerRelevant (DiffuseLayer {}) = pure True
materialLayerRelevant (TransparentLayer msrgba) = fmap isTransparentRelevant msrgba
materialLayerRelevant (EmissiveLayer msrgb) = fmap isEmissiveRelevant msrgb
materialLayerRelevant (SpecularLayer msrgb _) = fmap isEmissiveRelevant msrgb
materialLayerRelevant (CompoundLayer {}) = pure True
materialLayerRelevant (FilterLayer msrgb) = fmap isFilterRelevant msrgb
-- | Run an IO action wrapped in OpenGL state appropriate for the layer in question.
materialLayerToOpenGLWrapper :: MaterialLayer -> IO () -> IO ()
materialLayerToOpenGLWrapper (DiffuseLayer ms) io =
do cm <- get colorMaterial
materialEmission FrontAndBack $= Color4 0 0 0 1
materialSpecular FrontAndBack $= Color4 0 0 0 1
colorMaterial $= Just (FrontAndBack,AmbientAndDiffuse)
maybe (return ()) (color . colorToOpenGL) $ fromPure ms
io
colorMaterial $= cm
materialLayerToOpenGLWrapper (TransparentLayer ms) io =
do cm <- get colorMaterial
materialEmission FrontAndBack $= Color4 0 0 0 1
materialSpecular FrontAndBack $= Color4 0 0 0 1
colorMaterial $= Just (FrontAndBack,AmbientAndDiffuse)
maybe (return ()) (color . colorToOpenGL) $ fromPure ms
alphaBlendWrapper io
colorMaterial $= cm
materialLayerToOpenGLWrapper (EmissiveLayer ms) io =
do l <- get lighting
lighting $= Disabled
maybe (return ()) (color . colorToOpenGL) $ fromPure ms
additiveBlendWrapper io
lighting $= l
materialLayerToOpenGLWrapper (SpecularLayer ms shininess) io =
do cm <- get colorMaterial
lmlv <- get lightModelLocalViewer
materialShininess FrontAndBack $= shininess
materialAmbientAndDiffuse FrontAndBack $= Color4 0 0 0 1
materialEmission FrontAndBack $= Color4 0 0 0 1
colorMaterial $= Just (FrontAndBack,Specular)
lightModelLocalViewer $= Enabled
maybe (return ()) (color . colorToOpenGL) $ fromPure ms
additiveBlendWrapper io
colorMaterial $= cm
lightModelLocalViewer $= lmlv
materialLayerToOpenGLWrapper (CompoundLayer ms specular_rgb emissive_rgb shininess) io =
do cm <- get colorMaterial
lmlv <- get lightModelLocalViewer
materialSpecular FrontAndBack $= (\(RGB r g b) -> Color4 (f2f r) (f2f g) (f2f b) 1) specular_rgb
materialShininess FrontAndBack $= shininess
materialEmission FrontAndBack $= (\(RGB r g b) -> Color4 (f2f r) (f2f g) (f2f b) 1) emissive_rgb
colorMaterial $= Just (FrontAndBack,AmbientAndDiffuse)
lightModelLocalViewer $= Enabled
maybe (return ()) (color . colorToOpenGL) $ fromPure ms
io
colorMaterial $= cm
lightModelLocalViewer $= lmlv
materialLayerToOpenGLWrapper (FilterLayer ms) io =
do l <- get lighting
lighting $= Disabled
maybe (return ()) (color . colorToOpenGL) $ fromPure ms
filterBlendWrapper io
lighting $= l
-- | Run an IO action with OpenGL blending state. Used for transparent surfaces.
alphaBlendWrapper :: IO () -> IO ()
alphaBlendWrapper io =
do bf <- get blendFunc
b <- get blend
blendFunc $= (SrcAlpha,OneMinusSrcAlpha)
blend $= Enabled
io
blendFunc $= bf
blend $= b
-- | Run an IO action with additive blending OpenGL state. Used for emissive surfaces.
additiveBlendWrapper :: IO () -> IO ()
additiveBlendWrapper io =
do bf <- get blendFunc
b <- get blend
blendFunc $= (One,One)
blend $= Enabled
io
blendFunc $= bf
blend $= b
-- | Rune an IO action with multiplicative blending OpenGL state. Used for filter surfaces.
filterBlendWrapper :: IO () -> IO ()
filterBlendWrapper io =
do bf <- get blendFunc
b <- get blend
blendFunc $= (DstColor,Zero)
blend $= Enabled
io
blendFunc $= bf
blend $= b
-- | A simple colored material.
diffuseLayer :: MaterialSurface RGB -> Material
diffuseLayer msrgb = Material [DiffuseLayer msrgb]
-- | A shiny material with specular highlight, including a specular exponent parameter.
-- Larger exponents give tighter specular highlights, but should be less than 128 (larger than
-- that wouldn't have very much effect anyway). Typical values are 1-10 or so.
--
specularLayer :: MaterialSurface RGB -> GLfloat -> Material
specularLayer msrgb x = Material [SpecularLayer msrgb x]
-- | A transparent colored material.
transparentLayer :: MaterialSurface RGBA -> Material
transparentLayer msrgba = Material [TransparentLayer msrgba]
-- | A material that seems to glow.
emissiveLayer :: MaterialSurface RGB -> Material
emissiveLayer msrgb = Material [EmissiveLayer msrgb]
-- | A material that doesn't reflect or emit life, but simply performs a multiplicative filter on whatever is behind it.
filteringLayer :: MaterialSurface RGB -> Material
filteringLayer msrgb = Material [FilterLayer msrgb]