packages feed

gloss-relative-0.1.0.0: src/Graphics/Gloss/Relative/Internal/Picture.hs

module Graphics.Gloss.Relative.Internal.Picture where

import Graphics.Gloss
import Graphics.Gloss.Relative.Internal.Dimension

import Data.Semigroup
import Data.Monoid

-- * Pictures

translateX :: Float -> Picture -> Picture
translateX x p = Translate x 0 p

translateY :: Float -> Picture -> Picture
translateY y p = Translate 0 y p

-- * Regions

-- | A rectangular region within the screen.
data Region = Region
    { regionLeftTop :: Point
    , regionDimension :: Dimension
    } deriving (Eq,Ord,Show)

instance Semigroup Region where
    (Region (x1,y1) (w1,h1)) <> (Region (x2,y2) (w2,h2)) = Region (xmin,ymin) (abs (xmax-xmin),abs (ymax-ymin))
         where xmin = min x1 x2
               ymin = min y1 y2
               xmax = max (x1+w1) (x2+w2)
               ymax = max (y1+h1) (y2+h2)

instance Monoid Region where
    mempty = Region (0,0) (0,0)

regionScreenSize :: Region -> ScreenSize
regionScreenSize (Region (x,y) (xdim,ydim)) = (ceiling $ maxX * 2,ceiling $ maxY * 2)
    where
    maxX = max (abs x) (abs $ x + xdim)
    maxY = max (abs y) (abs $ y + ydim)

dimensionToRegion :: Dimension -> Region
dimensionToRegion (w,h) = Region { regionLeftTop = (-w/2,h/2), regionDimension = (w,h) }

pointInsideRegion :: Point -> Region -> Bool
pointInsideRegion (mouseX,mouseY) r =
    let (regionLeft,regionTop) = regionLeftTop r in
    let (w,h) = regionDimension r in
    let regionRight = regionLeft + w in
    let regionBottom = regionTop - h in
    (regionLeft <= mouseX && mouseX <= regionRight) && (regionBottom <= mouseY && mouseY <= regionTop)

translateRegionX :: Float -> Region -> Region
translateRegionX f r = r { regionLeftTop = translatePointX f (regionLeftTop r) }

translateRegionY :: Float -> Region -> Region
translateRegionY f r = r { regionLeftTop = translatePointY f (regionLeftTop r) }

-- * Picture bounding box

-- | Estimates the 'Region' of a 'Picture'.
pictureRegion :: Picture -> Region
pictureRegion = bbToRegion . bbox

pictureScreenSize :: Picture -> ScreenSize
pictureScreenSize = regionScreenSize . pictureRegion 

pictureDimension :: Picture -> Dimension
pictureDimension = screenSizeToDimension . pictureScreenSize

type Elipsis = (Float,Float,Float,Float,Float,Float)
data BB = BB { bbElipsis :: [Elipsis], bbPoints :: [Point] } deriving Show

instance Semigroup BB where
    (BB es1 ps1) <> (BB es2 ps2) = BB (es1++es2) (ps1++ps2)
instance Monoid BB where
    mempty = BB [] []

bbox :: Picture -> BB
bbox Blank = mempty
bbox (Polygon l) = BB [] l
bbox (Line l) = BB [] l
bbox (Circle r) = case (circElipsis r) of
    Nothing -> mempty
    Just e -> BB [e] []
bbox (ThickCircle r thick) = case (circElipsis $ r + thick / 2) of
    Nothing -> mempty
    Just e -> BB [e] []
bbox (Arc _ _ r) = bbox (Circle r) -- big overapproximation
bbox (ThickArc _ _ r thick) = bbox (ThickCircle r thick) -- big overapproximation
bbox (Text str) = bbox $ Translate (textWidth/2) (fromIntegral charHeight/2) rect
    where
    rect = rectangleWire textWidth (realToFrac charHeight)
    textWidth = realToFrac (length str) * (realToFrac charWidth)
bbox (Bitmap bmp) = bbox $ rectangleWire (fromIntegral w) (fromIntegral h)
    where (w,h) = bitmapSize bmp
bbox (BitmapSection rect bmp) = bbox $ rectangleWire (fromIntegral w) (fromIntegral h)
    where (w,h) = rectSize rect
bbox (Color c p) = bbox p
bbox (Scale x y p) = scaleBB x y (bbox p)
bbox (Translate x y p) = translateBB x y (bbox p)
bbox (Rotate alpha p) = rotateBB alpha (bbox p)
bbox (Pictures xs) = mconcat $ map bbox xs

charHeight :: Int
charHeight =  104
charWidth :: Int
charWidth = 70

scaleBB :: Float -> Float -> BB -> BB
scaleBB x y (BB es ps) = BB (map (scaleElipsis x y) es) (scalePath x y ps)

translateBB :: Float -> Float -> BB -> BB
translateBB x y (BB es ps) = BB (map (translateElipsis x y) es) (translatePath x y ps)

rotateBB :: Float -> BB -> BB
rotateBB alpha (BB es ps) = BB (map (rotateElipsis alpha) es) (rotatePath alpha ps)

bbToRegion :: BB -> Region
bbToRegion (BB es ps) = mconcat $ pathRegion ps : map elipsisRegion es

pathRegion :: Path -> Region
pathRegion [] = mempty
pathRegion l = Region (xmin,ymin) (abs (xmax-xmin),abs (ymax-ymin))
 where (xs,ys) = unzip l
       xmin = minimum xs
       ymin = minimum ys
       xmax = maximum xs
       ymax = maximum ys

elipsisRegion :: Elipsis -> Region
elipsisRegion (a,b,c,d,e,f) | dlt==0 = mempty
                        | otherwise = Region (xl,yb) (abs (xr-xl),abs (yt-yb))
 where dlt = 4*a*c - b^2
       xc = (b*e - 2*c*d) / dlt
       yc = (b*d - 2*a*e) / dlt
       xr = xc + sqrt ((2*b*e-4*c*d)^2+4*dlt*(e^2-4*c*f)) / (2*dlt)
       yt = yc + sqrt ((2*b*d-4*a*e)^2+4*dlt*(d^2-4*a*f)) / (2*dlt)
       xl = xc - sqrt ((2*b*e-4*c*d)^2+4*dlt*(e^2-4*c*f)) / (2*dlt)
       yb = yc - sqrt ((2*b*d-4*a*e)^2+4*dlt*(d^2-4*a*f)) / (2*dlt)

-- converts point from cartesian to polar coordinates
toPolar :: Point -> (Float,Float)
toPolar (x,y) = (sqrt (x^2+y^2), atan2 y x)

-- converts point from polar to cartesian
toCartesian :: (Float,Float) -> Point
toCartesian (r,gama) = (r*cos gama,r*sin gama)

-- a circle as an elipsis
circElipsis :: Float -> Maybe Elipsis
circElipsis 0 = Nothing
circElipsis r = Just (1/r^2,0,1/r^2,0,0,-1)

rotatePoint :: Float -> Point -> Point
rotatePoint alpha (x,y) = toCartesian (r,gama + (rad (-alpha)))
 where (r,gama) = toPolar (x,y)
 
rotatePath :: Float -> Path -> Path
rotatePath alpha = map (rotatePoint alpha)

rotateElipsis :: Float -> Elipsis -> Elipsis
rotateElipsis alpha (a,b,c,d,e,f) = (a',b',c',d',e',f')
 where theta = rad (alpha)
       a' = a*(cos theta)^2 + b*sin theta*cos theta + c*(sin theta)^2
       b' = 2*(c-a)*sin theta*cos theta + b*((cos theta)^2-(sin theta)^2)
       c' = a*(sin theta)^2 - b*sin theta*cos theta + c*(cos theta)^2
       d' = d*cos theta + e*sin theta
       e' = -d*sin theta + e*cos theta
       f' = f

translatePoint :: Float -> Float -> Point -> Point
translatePoint x y (a,b) = (a+x,b+y)

translatePointX :: Float -> Point -> Point
translatePointX f (x,y) = (x+f,y)

translatePointY :: Float -> Point -> Point
translatePointY f (x,y) = (x,y+f)

translatePath :: Float -> Float -> Path -> Path
translatePath x y = map (translatePoint x y)

translateElipsis :: Float -> Float -> Elipsis -> Elipsis
translateElipsis x y (a,b,c,d,e,f) = (a',b',c',d',e',f')
 where a' = a
       b' = b
       c' = c
       d' = d - 2*a*x - b*y
       e' = e - b*x - 2*c*y
       f' = f + a*x^2 + b*x*y + c*y^2 - d*x -e*y

scalePoint :: Float -> Float -> Point -> Point
scalePoint xs ys (x,y) = (x*xs,y*ys)

scalePath :: Float -> Float -> Path -> Path
scalePath xs ys = map (scalePoint xs ys)

scaleElipsis :: Float -> Float -> Elipsis -> Elipsis
scaleElipsis xs ys (a,b,c,d,e,f) = (a',b',c',d',e',f')
 where a' = a / (xs^2)
       b' = b / (xs * ys)
       c' = c / (ys^2)
       d' = d / xs
       e' = e / ys
       f' = f

mulPointwise :: Point -> Point -> Point
mulPointwise (a,b) (c,d) = (a*c,b*d)

-- * Auxiliary functions

-- | Converts degrees to radians
rad :: Float -> Float
rad alpha = alpha * pi / 180

-- | Converts radians to degrees
deg :: Float -> Float
deg alpha = alpha * 180 / pi