{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE ExistentialQuantification  #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell            #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Diagrams.ThreeD.Attributes
-- Copyright   :  (c) 2014 diagrams-lib team (see LICENSE)
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  diagrams-discuss@googlegroups.com
--
-- Diagrams may have /attributes/ which affect the way they are
-- rendered.  This module defines some common attributes relevant in
-- 3D; particular backends may also define more backend-specific
-- attributes.
--
-- Every attribute type must have a /semigroup/ structure, that is, an
-- associative binary operation for combining two attributes into one.
-- Unless otherwise noted, all the attributes defined here use the
-- 'Last' structure, that is, combining two attributes simply keeps
-- the second one and throws away the first.  This means that child
-- attributes always override parent attributes.
--
-----------------------------------------------------------------------------

module Diagrams.ThreeD.Attributes where

import Control.Lens
import Data.Semigroup
import Data.Typeable

import Data.Colour

import Diagrams.Core

-- | @SurfaceColor@ is the inherent pigment of an object, assumed to
-- be opaque.
newtype SurfaceColor = SurfaceColor (Last (Colour Double))
                     deriving (Typeable, Semigroup)
instance AttributeClass SurfaceColor

surfaceColor :: Iso' SurfaceColor (Colour Double)
surfaceColor = iso (\(SurfaceColor (Last c)) -> c) (SurfaceColor . Last)

-- | Set the surface color.
sc :: HasStyle d => Colour Double -> d -> d
sc = applyAttr . review surfaceColor

-- | @Diffuse@ is the fraction of incident light reflected diffusely,
-- that is, in all directions.  The actual light reflected is the
-- product of this value, the incident light, and the @SurfaceColor@
-- Attribute.  For physical reasonableness, @Diffuse@ should have a
-- value between 0 and 1; this is not checked.
newtype Diffuse = Diffuse (Last Double)
                deriving (Typeable, Semigroup)
instance AttributeClass Diffuse

_Diffuse :: Iso' Diffuse Double
_Diffuse = iso (\(Diffuse (Last d)) -> d) (Diffuse . Last)

-- | Set the diffuse reflectance.
diffuse :: HasStyle d => Double -> d -> d
diffuse = applyAttr . review _Diffuse

-- | @Ambient@ is an ad-hoc representation of indirect lighting.  The
-- product of @Ambient@ and @SurfaceColor@ is added to the light
-- leaving an object due to diffuse and specular terms.  @Ambient@ can
-- be set per-object, and can be loosely thought of as the product of
-- indirect lighting incident on that object and the diffuse
-- reflectance.
newtype Ambient = Ambient (Last Double)
                deriving (Typeable, Semigroup)
instance AttributeClass Ambient

_Ambient :: Iso' Ambient Double
_Ambient = iso (\(Ambient (Last d)) -> d) (Ambient . Last)

-- | Set the emittance due to ambient light.
ambient :: HasStyle d => Double -> d -> d
ambient = applyAttr . review _Ambient

-- | A specular highlight has two terms, the intensity, between 0 and
-- 1, and the size.  The highlight size is assumed to be the exponent
-- in a Phong shading model (though Backends are free to use a
-- different shading model).  In this model, reasonable values are
-- between 1 and 50 or so, with higher values for shinier objects.
-- Physically, the intensity and the value of @Diffuse@ must add up to
-- less than 1; this is not enforced.
data Specular = Specular { _specularIntensity :: Double
                         , _specularSize :: Double
                         }

makeLenses ''Specular

newtype Highlight = Highlight (Last Specular)
                    deriving (Typeable, Semigroup)
instance AttributeClass Highlight

_Highlight :: Iso' Highlight Specular
_Highlight = iso (\(Highlight (Last s)) -> s) (Highlight . Last)

-- | Set the specular highlight.
highlight :: HasStyle d => Specular -> d -> d
highlight = applyAttr . review _Highlight