packages feed

tinytools-0.1.0.0: src/Potato/Flow/Methods/LineTypes.hs

{-# LANGUAGE RecordWildCards #-}

module Potato.Flow.Methods.LineTypes where

import           Relude

import           Potato.Flow.Math
import           Potato.Flow.SElts


import Data.Default

import Linear.Vector ((^*))
import Linear.Matrix (M22, (!*))
import Data.Ratio

import Control.Exception (assert)


data CartDir = CD_Up | CD_Down | CD_Left | CD_Right deriving (Eq, Generic, Show)
instance NFData CartDir


data AnchorType = AT_End_Up | AT_End_Down | AT_End_Left | AT_End_Right | AT_Elbow_TL | AT_Elbow_TR | AT_Elbow_BR | AT_Elbow_BL | AT_Elbow_Invalid deriving (Eq, Show)

flipCartDir :: CartDir -> CartDir
flipCartDir = \case
  CD_Up -> CD_Down
  CD_Down -> CD_Up
  CD_Left -> CD_Right
  CD_Right -> CD_Left

cartDirToUnit :: CartDir -> XY
cartDirToUnit = \case
  CD_Up -> V2 0 (-1)
  CD_Down -> V2 0 1
  CD_Left -> V2 (-1) 0
  CD_Right -> V2 1 0

cartDirToAnchor :: CartDir -> Maybe CartDir -> AnchorType
cartDirToAnchor start mnext = case mnext of
  Nothing -> case start of
    CD_Up -> AT_End_Up
    CD_Down -> AT_End_Down
    CD_Left -> AT_End_Left
    CD_Right -> AT_End_Right
  Just next -> case start of
    CD_Up -> case next of
      CD_Left -> AT_Elbow_TR
      CD_Right -> AT_Elbow_TL
      _ -> AT_Elbow_Invalid
    CD_Down -> case next of
      CD_Left -> AT_Elbow_BR
      CD_Right -> AT_Elbow_BL
      _ -> AT_Elbow_Invalid
    CD_Left -> case next of
      CD_Up -> AT_Elbow_BL
      CD_Down -> AT_Elbow_TL
      _ -> AT_Elbow_Invalid
    CD_Right -> case next of
      CD_Up -> AT_Elbow_BR
      CD_Down -> AT_Elbow_TR
      _ -> AT_Elbow_Invalid

cartDirWithDistanceToV2 :: (CartDir, Int, Bool) -> V2 Int
cartDirWithDistanceToV2 (cd, d, _) = cartDirToUnit cd ^* d


data LineAnchorsForRender = LineAnchorsForRender {
  _lineAnchorsForRender_start :: XY
  -- `Bool` parameter is whether we are at the start of a subsegment (i.e. a midpoint or endpoint)
  , _lineAnchorsForRender_rest :: [(CartDir, Int, Bool)]
} deriving (Show, Generic, Eq)

instance NFData LineAnchorsForRender


instance TransformMe LineAnchorsForRender where
  transformMe_rotateLeft LineAnchorsForRender {..} = LineAnchorsForRender {
      _lineAnchorsForRender_start = transformMe_rotateLeft _lineAnchorsForRender_start
      ,_lineAnchorsForRender_rest = fmap (\(cd,d,s) -> (transformMe_rotateLeft cd, d, s)) _lineAnchorsForRender_rest
    }
  transformMe_rotateRight LineAnchorsForRender {..} = LineAnchorsForRender {
      _lineAnchorsForRender_start = transformMe_rotateRight _lineAnchorsForRender_start
      ,_lineAnchorsForRender_rest = fmap (\(cd,d,s) -> (transformMe_rotateRight cd, d, s)) _lineAnchorsForRender_rest
    }
  transformMe_reflectHorizontally LineAnchorsForRender {..} = LineAnchorsForRender {
      _lineAnchorsForRender_start = transformMe_reflectHorizontally _lineAnchorsForRender_start
      ,_lineAnchorsForRender_rest = fmap (\(cd,d,s) -> (transformMe_reflectHorizontally cd, d, s)) _lineAnchorsForRender_rest
    }


-- NOTE our coordinate system is LEFT HANDED
--  --> +x
-- |
-- v
-- +y
matrix_cw_90 :: M22 Int
matrix_cw_90 = V2 (V2 0 (-1)) (V2 1 0)
matrix_ccw_90 :: M22 Int
matrix_ccw_90 = V2 (V2 0 1) (V2 (-1) 0)

-- TODO rename me so it include reflection
-- TODO rename so it's lower case
class TransformMe a where
  -- CCW
  transformMe_rotateLeft :: a -> a
  transformMe_rotateLeft = transformMe_rotateRight . transformMe_rotateRight . transformMe_rotateRight
  -- CW
  transformMe_rotateRight :: a -> a
  transformMe_rotateRight = transformMe_rotateLeft . transformMe_rotateLeft . transformMe_rotateLeft

  transformMe_reflectHorizontally :: a -> a
  transformMe_reflectHorizontally = transformMe_rotateLeft . transformMe_rotateLeft . transformMe_reflectVertically

  transformMe_reflectVertically :: a -> a
  transformMe_reflectVertically = transformMe_rotateLeft . transformMe_rotateLeft . transformMe_reflectHorizontally

instance TransformMe AttachmentLocation where
  transformMe_rotateLeft = \case
    AL_Top -> AL_Left
    AL_Bot -> AL_Right
    AL_Left -> AL_Bot
    AL_Right -> AL_Top
    AL_Any -> AL_Any
  transformMe_rotateRight = \case
    AL_Top -> AL_Right
    AL_Bot -> AL_Left
    AL_Left -> AL_Top
    AL_Right -> AL_Bot
    AL_Any -> AL_Any
  transformMe_reflectHorizontally = \case
    AL_Left -> AL_Right
    AL_Right -> AL_Left
    x -> x


instance TransformMe CartDir where
  transformMe_rotateLeft = \case
    CD_Up -> CD_Left
    CD_Down -> CD_Right
    CD_Left -> CD_Down
    CD_Right -> CD_Up
  transformMe_rotateRight = \case
    CD_Up -> CD_Right
    CD_Down -> CD_Left
    CD_Left -> CD_Up
    CD_Right -> CD_Down
  transformMe_reflectHorizontally = \case
    CD_Right -> CD_Left
    CD_Left -> CD_Right
    x -> x

instance TransformMe AnchorType where
  transformMe_rotateLeft = \case
    AT_End_Up -> AT_End_Left
    AT_End_Down -> AT_End_Right
    AT_End_Left -> AT_End_Down
    AT_End_Right -> AT_End_Up
    AT_Elbow_TL -> AT_Elbow_BL
    AT_Elbow_TR -> AT_Elbow_TL
    AT_Elbow_BR -> AT_Elbow_TR
    AT_Elbow_BL -> AT_Elbow_BR
    AT_Elbow_Invalid -> AT_Elbow_Invalid
  transformMe_rotateRight = \case
    AT_End_Up -> AT_End_Right
    AT_End_Down -> AT_End_Left
    AT_End_Left -> AT_End_Up
    AT_End_Right -> AT_End_Down
    AT_Elbow_TL -> AT_Elbow_TR
    AT_Elbow_TR -> AT_Elbow_BR
    AT_Elbow_BR -> AT_Elbow_BL
    AT_Elbow_BL -> AT_Elbow_TL
    AT_Elbow_Invalid -> AT_Elbow_Invalid
  transformMe_reflectHorizontally = \case
    AT_End_Left -> AT_End_Right
    AT_End_Right -> AT_End_Left
    AT_Elbow_TL -> AT_Elbow_TR
    AT_Elbow_TR -> AT_Elbow_TL
    AT_Elbow_BR -> AT_Elbow_BL
    AT_Elbow_BL -> AT_Elbow_BR
    AT_Elbow_Invalid -> AT_Elbow_Invalid

instance TransformMe XY where
  transformMe_rotateLeft p = (!*) matrix_ccw_90 p - (V2 0 1)
  transformMe_rotateRight p = (!*) matrix_cw_90 p - (V2 1 0)
  transformMe_reflectHorizontally (V2 x y) = V2 (-(x+1)) y

instance (TransformMe a, TransformMe b) => TransformMe (a,b) where
  transformMe_rotateLeft (a,b) = (transformMe_rotateLeft a, transformMe_rotateLeft b)
  transformMe_rotateRight (a,b) = (transformMe_rotateRight a, transformMe_rotateRight b)
  transformMe_reflectHorizontally (a,b) = (transformMe_reflectHorizontally a, transformMe_reflectHorizontally b)

instance (TransformMe a, TransformMe b, TransformMe c) => TransformMe (a,b,c) where
  transformMe_rotateLeft (a,b,c) = (transformMe_rotateLeft a, transformMe_rotateLeft b, transformMe_rotateLeft c)
  transformMe_rotateRight (a,b,c) = (transformMe_rotateRight a, transformMe_rotateRight b, transformMe_rotateRight c)
  transformMe_reflectHorizontally (a,b,c) = (transformMe_reflectHorizontally a, transformMe_reflectHorizontally b, transformMe_reflectHorizontally c)


-- NOTE assumes LBox is Canonical
instance TransformMe LBox where
  transformMe_rotateLeft lbox@(LBox tl (V2 w h)) = assert (lBox_isCanonicalLBox lbox) r where
    V2 blx bly = (!*) matrix_ccw_90 tl
    r = LBox (V2 blx (bly - w)) (V2 h w)
  transformMe_rotateRight lbox@(LBox tl (V2 w h)) = assert (lBox_isCanonicalLBox lbox) r where
    V2 trx try = (!*) matrix_cw_90 tl
    r = LBox (V2 (trx-h) try) (V2 h w)
  transformMe_reflectHorizontally lbox@(LBox (V2 x y) (V2 w h)) = assert (lBox_isCanonicalLBox lbox) r where
    r = LBox (V2 (-(x+w)) y) (V2 w h)



-- very specific to the way AttachmentOffsetRatio is associated with a certain side of a box
instance TransformMe AttachmentOffsetRatio where
  transformMe_rotateLeft = id
  transformMe_rotateRight = id
  transformMe_reflectHorizontally r = (d-n) % d where
    n = numerator r
    d = denominator r


-- TODO UTs for CartRotationReflection stuff
-- apply rotation first, then apply reflections
data CartRotationReflection = CartRotationReflection {
  _cartRotationReflection_rotateLeftTimes :: Int -- number of times we rotated left
  , _cartRotationReflection_reflectVertical :: Bool -- did we reflect accross vertical axis
}

instance TransformMe CartRotationReflection where
  transformMe_rotateLeft x@CartRotationReflection {..} = if _cartRotationReflection_reflectVertical
    then x { _cartRotationReflection_rotateLeftTimes = (_cartRotationReflection_rotateLeftTimes + 3) `mod` 4 }
    else x { _cartRotationReflection_rotateLeftTimes = (_cartRotationReflection_rotateLeftTimes + 1) `mod` 4 }
  transformMe_reflectHorizontally x@CartRotationReflection {..} = x { _cartRotationReflection_reflectVertical = not _cartRotationReflection_reflectVertical }

cartRotationReflection_identity :: CartRotationReflection
cartRotationReflection_identity = CartRotationReflection {
    _cartRotationReflection_rotateLeftTimes = 0
    , _cartRotationReflection_reflectVertical = False
  }
cartRotationReflection_invert :: CartRotationReflection -> CartRotationReflection
cartRotationReflection_invert x@CartRotationReflection {..} = if _cartRotationReflection_reflectVertical
  then x
  else x { _cartRotationReflection_rotateLeftTimes = (_cartRotationReflection_rotateLeftTimes + 3) `mod` 4 }

cartRotationReflection_invert_apply :: (TransformMe a) => CartRotationReflection -> a -> a
cartRotationReflection_invert_apply crr a = cartRotationReflection_apply (cartRotationReflection_invert crr) a

-- | Apply a function @n@ times to a given value.
nTimes :: Int -> (a -> a) -> (a -> a)
nTimes 0 _ = id
nTimes 1 f = f
nTimes n f = f . nTimes (n-1) f

cartRotationReflection_apply :: (TransformMe a) => CartRotationReflection -> a -> a
cartRotationReflection_apply CartRotationReflection {..} a = r where
  nrl = _cartRotationReflection_rotateLeftTimes `mod` 4
  r' = nTimes nrl transformMe_rotateLeft a
  r = if _cartRotationReflection_reflectVertical then transformMe_reflectVertically a else a