content: {-# LANGUAGE BangPatterns #-}
  {-# LANGUAGE MagicHash    #-}
  {-# OPTIONS_HADDOCK hide #-}
  
  -- | Fast(ish) rendering of circles.
  module Graphics.Gloss.Internals.Rendering.Circle
          ( renderCircle
          , renderArc)
  where
  import  Graphics.Gloss.Internals.Rendering.Common
  import  GHC.Exts
  import  qualified Graphics.Rendering.OpenGL.GL          as GL
  
  
  -------------------------------------------------------------------------------
  -- | Decide how many line segments to use to render the circle.
  --   The number of segments we should use to get a nice picture depends on
  --   the size of the circle on the screen, not its intrinsic radius.
  --   If the viewport has been zoomed-in then we need to use more segments.
  circleSteps :: Float -> Int
  circleSteps sDiam
          | sDiam < 8     = 8
          | sDiam < 16    = 16
          | sDiam < 32    = 32
          | otherwise     = 64
  {-# INLINE circleSteps #-}
  
  
  -- Circle ---------------------------------------------------------------------
  -- | Render a circle with the given thickness
  renderCircle :: Float -> Float -> Float -> Float -> Float -> IO ()
  renderCircle posX posY scaleFactor radius_ thickness_
   = go (abs radius_) (abs thickness_)
   where go radius thickness
  
          -- If the circle is smaller than a pixel, render it as a point.
          | thickness     == 0
          , radScreen     <- scaleFactor * (radius + thickness / 2)
          , radScreen     <= 1
          = GL.renderPrimitive GL.Points
              $ GL.vertex $ GL.Vertex2 (gf posX) (gf posY)
  
          -- Render zero thickness circles with lines.
          | thickness == 0
          , radScreen     <- scaleFactor * radius
          , steps         <- circleSteps radScreen
          = renderCircleLine  posX posY steps radius
  
          -- Some thick circle.
          | radScreen     <- scaleFactor * (radius + thickness / 2)
          , steps         <- circleSteps radScreen
          = renderCircleStrip posX posY steps radius thickness
  
  
  -- | Render a circle as a line.
  renderCircleLine :: Float -> Float -> Int -> Float -> IO ()
  renderCircleLine (F# posX) (F# posY) steps (F# rad)
   = let  n               = fromIntegral steps
          !(F# tStep)     = (2 * pi) / n
          !(F# tStop)     = (2 * pi)
  
     in   GL.renderPrimitive GL.LineLoop
           $ renderCircleLine_step posX posY tStep tStop rad 0.0#
  {-# INLINE renderCircleLine #-}
  
  
  -- | Render a circle with a given thickness as a triangle strip
  renderCircleStrip :: Float -> Float -> Int -> Float -> Float -> IO ()
  renderCircleStrip (F# posX) (F# posY) steps r width
   = let  n               = fromIntegral steps
          !(F# tStep)     = (2 * pi) / n
          !(F# tStop)     = (2 * pi) + (F# tStep) / 2
          !(F# r1)        = r - width / 2
          !(F# r2)        = r + width / 2
  
     in   GL.renderPrimitive GL.TriangleStrip
           $ renderCircleStrip_step posX posY tStep tStop r1 0.0# r2
                  (tStep `divideFloat#` 2.0#)
  {-# INLINE renderCircleStrip #-}
  
  
  -- Arc ------------------------------------------------------------------------
  -- | Render an arc with the given thickness.
  renderArc
   :: Float -> Float -> Float -> Float -> Float -> Float -> Float -> IO ()
  renderArc posX posY scaleFactor radius_ a1 a2 thickness_
   = go (abs radius_) (abs thickness_)
   where
         go radius thickness
          -- Render zero thickness arcs with lines.
          | thickness == 0
          , radScreen     <- scaleFactor * radius
          , steps         <- circleSteps radScreen
          = renderArcLine posX posY steps radius a1 a2
  
          -- Some thick arc.
          | radScreen     <- scaleFactor * (radius + thickness / 2)
          , steps         <- circleSteps radScreen
          = renderArcStrip posX posY steps radius a1 a2 thickness
  
  
  -- | Render an arc as a line.
  renderArcLine
   :: Float -> Float -> Int -> Float -> Float -> Float -> IO ()
  renderArcLine (F# posX) (F# posY) steps (F# rad) a1 a2
   = let  n               = fromIntegral steps
          !(F# tStep)     = (2 * pi) / n
          !(F# tStart)    = degToRad a1
          !(F# tStop)     = degToRad a2 + if a1 >= a2 then 2 * pi else 0
  
          -- force the line to end at the desired angle
          endVertex       = addPointOnCircle posX posY rad tStop
  
     in   GL.renderPrimitive GL.LineStrip
           $ do   renderCircleLine_step posX posY tStep tStop rad tStart
                  endVertex
  {-# INLINE renderArcLine #-}
  
  
  -- | Render an arc with a given thickness as a triangle strip
  renderArcStrip
   :: Float -> Float -> Int -> Float -> Float -> Float -> Float -> IO ()
  renderArcStrip (F# posX) (F# posY) steps r a1 a2 width
   = let  n               = fromIntegral steps
          tStep           = (2 * pi) / n
  
          t1              = normalizeAngle $ degToRad a1
  
          a2'             = normalizeAngle $ degToRad a2
          t2              = if a2' == 0 then 2*pi else a2'
  
          (tStart, tStop) = if t1 <= t2 then (t1, t2) else (t2, t1)
          tDiff           = tStop - tStart
          tMid            = tStart + tDiff / 2
  
          !(F# tStep')    = tStep
          !(F# tStep2')   = tStep / 2
          !(F# tStart')   = tStart
          !(F# tStop')    = tStop
          !(F# tCut')     = tStop - tStep
          !(F# tMid')     = tMid
          !(F# r1')       = r - width / 2
          !(F# r2')       = r + width / 2
  
     in   GL.renderPrimitive GL.TriangleStrip
           $ do   -- start vector
                  addPointOnCircle posX posY r1' tStart'
                  addPointOnCircle posX posY r2' tStart'
  
                  -- If we don't have a complete step then just drop a point
                  -- between the two ending lines.
                  if tDiff < tStep
                    then do
                          addPointOnCircle posX posY r1' tMid'
  
                          -- end vectors
                          addPointOnCircle posX posY r2' tStop'
                          addPointOnCircle posX posY r1' tStop'
  
                    else do
                          renderCircleStrip_step posX posY
                                  tStep' tCut' r1' tStart' r2'
                                  (tStart' `plusFloat#` tStep2')
  
                          -- end vectors
                          addPointOnCircle posX posY r1' tStop'
                          addPointOnCircle posX posY r2' tStop'
  {-# INLINE renderArcStrip #-}
  
  
  -- Step functions -------------------------------------------------------------
  renderCircleLine_step
          :: Float# -> Float#
          -> Float# -> Float#
          -> Float# -> Float#
          -> IO ()
  
  renderCircleLine_step posX posY tStep tStop rad tt
          | 1# <- tt `geFloat#` tStop
          = return ()
  
          | otherwise
          = do    addPointOnCircle posX posY rad tt
                  renderCircleLine_step posX posY tStep tStop rad
                          (tt `plusFloat#` tStep)
  {-# INLINE renderCircleLine_step #-}
  
  
  renderCircleStrip_step
          :: Float# -> Float#
          -> Float# -> Float#
          -> Float# -> Float#
          -> Float# -> Float# -> IO ()
  
  renderCircleStrip_step posX posY tStep tStop r1 t1 r2 t2
          | 1# <- t1 `geFloat#` tStop
          = return ()
  
          | otherwise
          = do    addPointOnCircle posX posY r1 t1
                  addPointOnCircle posX posY r2 t2
                  renderCircleStrip_step posX posY tStep tStop r1
                          (t1 `plusFloat#` tStep) r2 (t2 `plusFloat#` tStep)
  {-# INLINE renderCircleStrip_step #-}
  
  
  addPoint :: Float# -> Float# -> IO ()
  addPoint x y =
    GL.vertex $ GL.Vertex2 (gf (F# x)) (gf (F# y))
  {-# INLINE addPoint #-}
  
  
  addPointOnCircle :: Float# -> Float# -> Float# -> Float# -> IO ()
  addPointOnCircle posX posY rad tt =
    addPoint
      (posX `plusFloat#` (rad `timesFloat#` (cosFloat# tt)))
      (posY `plusFloat#` (rad `timesFloat#` (sinFloat# tt)))
  {-# INLINE addPointOnCircle #-}
  
  
  -- | Convert degrees to radians
  degToRad :: Float -> Float
  degToRad d      = d * pi / 180
  {-# INLINE degToRad #-}
  
  
  -- | Normalise an angle to be between 0 and 2*pi radians
  normalizeAngle :: Float -> Float
  normalizeAngle f = f - 2 * pi * floor' (f / (2 * pi))
   where  floor' :: Float -> Float
          floor' x = fromIntegral (floor x :: Int)
  {-# INLINE normalizeAngle #-}
  
  
  {- Unused sector drawing code.
     Sectors are currently drawn as compound Pictures,
     but we might want this if we end up implementing the ThickSector
     version as well.
  
  -- | Render a sector as a line.
  renderSectorLine :: Float -> Float -> Int -> Float -> Float -> Float -> IO ()
  renderSectorLine pX@(F# posX) pY@(F# posY) steps (F# rad) a1 a2
   = let  n               = fromIntegral steps
          !(F# tStep)     = (2 * pi) / n
          !(F# tStart)    = degToRad a1
          !(F# tStop)     = degToRad a2 + if a1 >= a2 then 2 * pi else 0
  
          -- need to set up the edges of the start/end triangles
          startVertex     = GL.vertex $ GL.Vertex2 (gf pX) (gf pY)
          endVertex       = addPointOnCircle posX posY rad tStop
  
     in   GL.renderPrimitive GL.LineLoop
           $ do   startVertex
                  renderCircleLine_step posX posY tStep tStop rad tStart
                  endVertex
  
  -- | Render a sector.
  renderSector :: Float -> Float -> Float -> Float -> Float -> Float -> IO ()
  renderSector posX posY scaleFactor radius a1 a2
          | radScreen     <- scaleFactor * radius
          , steps         <- circleSteps (2 * radScreen)
          = renderSectorLine posX posY steps radius a1 a2
  -}
  
  