--------------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Rendering.OpenGL.GL.Points
-- Copyright   :  (c) Sven Panne 2002-2016
-- License     :  BSD3
-- 
-- Maintainer  :  Sven Panne <svenpanne@gmail.com>
-- Stability   :  stable
-- Portability :  portable
--
-- This module corresponds to section 3.3 (Points) of the OpenGL 2.1 specs.
--
--------------------------------------------------------------------------------

module Graphics.Rendering.OpenGL.GL.Points (
   -- * Point Rasterization
   pointSize, vertexProgramPointSize,

   -- * Controlling the Derived Size
   pointSizeRange, pointDistanceAttenuation,

   -- * Fading Points
   pointFadeThresholdSize,

   -- * Point Antialiasing
   pointSmooth,

   -- * Point Sprites
   pointSprite,

   -- * Implementation-Dependent Limits
   aliasedPointSizeRange, smoothPointSizeRange, smoothPointSizeGranularity
) where

import Control.Monad
import Data.StateVar
import Foreign.Marshal.Array
import Graphics.Rendering.OpenGL.GL.Capability
import Graphics.Rendering.OpenGL.GL.PointParameter
import Graphics.Rendering.OpenGL.GL.QueryUtils
import Graphics.GL

--------------------------------------------------------------------------------

-- | 'pointSize' contains the rasterized diameter of both aliased and
-- antialiased points. The initial value is 1. Using a point size other than 1
-- has different effects, depending on whether point antialiasing is enabled
-- (see 'pointSmooth') or point sprites are enabled (see 'pointSprite'). Both
-- are initially disabled.
--
-- The specified point size is multiplied with a distance attenuation factor
-- and clamped to the specified 'pointSizeRange', and further clamped to the
-- implementation-dependent point size range to produce the derived point size
-- using
--
-- @   /derivedSize/ = /clamp/ (/size/ * /sqrt/ (1 \/ (/a/ + /b/ * /d/ + /c/ * /d/^2)))@
--
-- where /d/ is the eye-coordinate distance from the eye to the vertex, and /a/,
-- /b/, and /c/ are the distance attenuation coefficients (see
-- 'pointDistanceAttenuation').
--
-- If multisampling is disabled, the computed point size is used as the point\'s
-- width.
--
-- If multisampling is enabled, the point may be faded by modifying the point
-- alpha value (see 'Graphics.Rendering.OpenGL.GL.PerFragment.sampleCoverage')
-- instead of allowing the point width to go below a given
-- 'pointFadeThresholdSize'. In this case, the width is further modified in
-- the following manner:
--
-- @   /width/ = if /derivedSize/ >= /threshold/ then /derivedSize/ else /threshold/@
--
-- The point alpha value is modified by computing:
--
-- @   /alpha/ = if /derivedSize/ >= /threshold/ then 1 else (/derivedSize/ \/ /threshold/)^2@
--
-- If point antialiasing is disabled, the actual size is determined by rounding
-- the supplied size to the nearest integer. (If the rounding results in the
-- value 0, it is as if the point size were 1.) If the rounded size is odd,
-- then the center point (/x/, /y/) of the pixel fragment that represents
-- the point is computed as
--
-- @   (/x/, /y/) = (/floor/ /xw/ + 0.5, /floor/ /yw/ + 0.5)@
--
-- where /xw/ and /yw/ indicate window coordinates. All pixels that lie within
-- the square grid of the rounded size centered at (/x/, /y/) make up the
-- fragment. If the size is even, the center point is
--
-- @   (/x/, /y/) = (/floor/ (/xw/ + 0.5), /floor/ (/yw/ + 0.5))@
--
-- and the rasterized fragment\'s centers are the half-integer window
-- coordinates within the square of the rounded size centered at (/x/, /y/). All
-- pixel fragments produced in rasterizing a nonantialiased point are assigned
-- the same associated data, that of the vertex corresponding to the point.
--
-- If antialiasing is enabled, then point rasterization produces a fragment for
-- each pixel square that intersects the region lying within the circle having
-- diameter equal to the current point size and centered at the point\'s
-- (/xw/, /yw/). The coverage value for each fragment is the window coordinate
-- area of the intersection of the circular region with the corresponding pixel
-- square. This value is saved and used in the final rasterization step. The
-- data associated with each fragment is the data associated with the point
-- being rasterized.
--
-- Not all sizes are supported when point antialiasing is enabled. If an
-- unsupported size is requested, the nearest supported size is used.  Only size
-- 1 is guaranteed to be supported; others depend on the implementation. To
-- query the range of supported sizes for antialiased points and the size
-- difference between supported sizes within the range, query
-- 'smoothPointSizeRange' and 'smoothPointSizeGranularity', respectively. For
-- aliased points, query the supported range with 'aliasedPointSizeRange'.
--
-- The point size specified when 'pointSize' is set is always returned when it
-- is queried. Clamping and rounding for aliased and antialiased points have no
-- effect on the specified value.
--
-- A non-antialiased point size may be clamped to an implementation-dependent
-- maximum. Although this maximum cannot be queried, it must be no less than the
-- maximum value for antialiased points, rounded to the nearest integer value.
--
-- An 'Graphics.Rendering.OpenGL.GLU.Errors.InvalidValue' is generated if
-- 'pointSize' is set to a value less than or equal to zero.
--
-- An 'Graphics.Rendering.OpenGL.GLU.Errors.InvalidOperation' is generated if
-- 'pointSize' is set during
-- 'Graphics.Rendering.OpenGL.GL.BeginEnd.renderPrimitive'.

pointSize :: StateVar GLfloat
pointSize = makeStateVar (getFloat1 id GetPointSize) glPointSize

--------------------------------------------------------------------------------

vertexProgramPointSize :: StateVar Capability
vertexProgramPointSize = makeCapability CapVertexProgramPointSize

--------------------------------------------------------------------------------

-- | The range to which the derived point size is clamped, see 'pointSize'. Note
-- that the size is further clamped to the implementation-dependent limits, see
-- 'aliasedPointSizeRange' and 'smoothPointSizeRange'. The initial range is
-- (0, 1).
--
-- An 'Graphics.Rendering.OpenGL.GLU.Errors.InvalidValue' is generated if the
-- lower or upper bound of the range is set to a value less than zero. If the
-- lower bound is greater than the upper bound, the point size after clamping is
-- undefined, but no error is generated.

pointSizeRange :: StateVar (GLfloat, GLfloat)
pointSizeRange =
   makeStateVar
   (liftM2 (,) (getFloat1 id GetPointSizeMin) (getFloat1 id GetPointSizeMax))
   (\(sizeMin, sizeMax) -> do pointParameterf PointSizeMin sizeMin
                              pointParameterf PointSizeMax sizeMax)

--------------------------------------------------------------------------------

-- | The  constant, linear, and quadratic distance attenuation coefficients, see
-- 'pointSize'. The initial coefficients are (1, 0, 0).

pointDistanceAttenuation :: StateVar (GLfloat, GLfloat, GLfloat)
pointDistanceAttenuation =
   makeStateVar
      (getFloat3 (,,) GetPointDistanceAttenuation)
      (\(a, b, c) -> withArray [a, b, c] $
                        pointParameterfv PointDistanceAttenuation)

--------------------------------------------------------------------------------

-- | The threshold for alpha attenuation of points when multisampling is used,
-- see 'pointSize'. The initial threshold is 1.
--
-- An 'Graphics.Rendering.OpenGL.GLU.Errors.InvalidValue' is generated if the
-- threshold is set to a value less than zero.

pointFadeThresholdSize :: StateVar GLfloat
pointFadeThresholdSize =
   makeStateVar
      (getFloat1 id GetPointFadeThresholdSize)
      (pointParameterf PointFadeThresholdSize)

--------------------------------------------------------------------------------

-- | Controls whether point antialiasing is enabled. The initial state is
-- 'Graphics.Rendering.OpenGL.GL.Capability.Disabled'.

pointSmooth :: StateVar Capability
pointSmooth = makeCapability CapPointSmooth

--------------------------------------------------------------------------------

-- | Controls whether point sprites are enabled. The initial state is
-- 'Graphics.Rendering.OpenGL.GL.Capability.Disabled'. When point sprites are
-- enabled, the state of point antialiasing (i.e. 'pointSmooth') is ignored.

pointSprite :: StateVar Capability
pointSprite = makeCapability CapPointSprite

--------------------------------------------------------------------------------

-- | The smallest and largest supported size of aliased points.

aliasedPointSizeRange :: GettableStateVar (GLfloat, GLfloat)
aliasedPointSizeRange =
   makeGettableStateVar $ getFloat2 (,) GetAliasedPointSizeRange

-- | The smallest and largest supported size of antialiased points.

smoothPointSizeRange :: GettableStateVar (GLfloat, GLfloat)
smoothPointSizeRange =
   makeGettableStateVar $ getFloat2 (,) GetSmoothPointSizeRange

-- | The antialiased point size granularity, i.e. the size difference between
-- supported sizes.

smoothPointSizeGranularity :: GettableStateVar GLfloat
smoothPointSizeGranularity =
   makeGettableStateVar $ getFloat1 id GetSmoothPointSizeGranularity
