packages feed

moonlight-planar-1.0.0.0: src-hex/Moonlight/Hex/Element.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

-- | Canonical vertices and sides of native axial hexagonal cells.
module Moonlight.Hex.Element
  ( HexCorner (..)
  , HexVertex
  , hexVertexCoordinates
  , HexSide
  , hexSideEndpoints
  , HexBoundarySide
  , hexBoundaryDirection
  , hexBoundaryFrom
  , hexBoundaryTo
  , hexBoundaryIdentity
  , hexCellVertex
  , hexCellVertices
  , hexCellSide
  , hexCellSides
  , hexCellBoundarySide
  , hexCellBoundary
  ) where

import Control.DeepSeq (NFData)
import Data.List.NonEmpty (NonEmpty (..))
import GHC.Generics (Generic)
import Moonlight.Hex.Coordinate (HexCoord (..), HexDirection (..))

-- | Corners in counter-clockwise boundary order.
data HexCorner
  = HexEastCorner
  | HexNorthEastCorner
  | HexNorthWestCorner
  | HexWestCorner
  | HexSouthWestCorner
  | HexSouthEastCorner
  deriving stock (Eq, Ord, Show, Enum, Bounded, Generic)
  deriving anyclass (NFData)

-- | A vertex in the integer affine embedding of the global axial lattice.
data HexVertex = HexVertex !Integer !Integer
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

hexVertexCoordinates :: HexVertex -> (Integer, Integer)
hexVertexCoordinates (HexVertex x y) = (x, y)
{-# INLINE hexVertexCoordinates #-}

-- | An unoriented side with endpoints stored in canonical ascending order.
data HexSide = HexSide !HexVertex !HexVertex
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

hexSideEndpoints :: HexSide -> (HexVertex, HexVertex)
hexSideEndpoints (HexSide from to) = (from, to)
{-# INLINE hexSideEndpoints #-}

-- | One counter-clockwise cell-boundary side, together with the neighbouring
-- cell direction across it and its canonical unoriented identity.
data HexBoundarySide = HexBoundarySide
  !HexDirection
  !HexVertex
  !HexVertex
  !HexSide
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

hexBoundaryDirection :: HexBoundarySide -> HexDirection
hexBoundaryDirection (HexBoundarySide direction _ _ _) = direction
{-# INLINE hexBoundaryDirection #-}

hexBoundaryFrom :: HexBoundarySide -> HexVertex
hexBoundaryFrom (HexBoundarySide _ from _ _) = from
{-# INLINE hexBoundaryFrom #-}

hexBoundaryTo :: HexBoundarySide -> HexVertex
hexBoundaryTo (HexBoundarySide _ _ to _) = to
{-# INLINE hexBoundaryTo #-}

hexBoundaryIdentity :: HexBoundarySide -> HexSide
hexBoundaryIdentity (HexBoundarySide _ _ _ side) = side
{-# INLINE hexBoundaryIdentity #-}

hexCellVertex :: HexCoord -> HexCorner -> HexVertex
hexCellVertex (HexCoord q r) corner =
  let centerX = 3 * toInteger q
      centerY = 2 * toInteger r + toInteger q
      (offsetX, offsetY) = cornerOffset corner
   in HexVertex (centerX + offsetX) (centerY + offsetY)
{-# INLINE hexCellVertex #-}

hexCellVertices :: HexCoord -> NonEmpty HexVertex
hexCellVertices coordinate =
  hexCellVertex coordinate HexEastCorner
    :| [ hexCellVertex coordinate HexNorthEastCorner
       , hexCellVertex coordinate HexNorthWestCorner
       , hexCellVertex coordinate HexWestCorner
       , hexCellVertex coordinate HexSouthWestCorner
       , hexCellVertex coordinate HexSouthEastCorner
       ]

-- | The side shared with the cell in the given direction.
hexCellSide :: HexCoord -> HexDirection -> HexSide
hexCellSide coordinate direction =
  let (fromCorner, toCorner) = directionCorners direction
   in canonicalSide
        (hexCellVertex coordinate fromCorner)
        (hexCellVertex coordinate toCorner)
{-# INLINE hexCellSide #-}

hexCellSides :: HexCoord -> NonEmpty HexSide
hexCellSides coordinate =
  hexCellSide coordinate HexEast
    :| [ hexCellSide coordinate HexNorthEast
       , hexCellSide coordinate HexNorthWest
       , hexCellSide coordinate HexWest
       , hexCellSide coordinate HexSouthWest
       , hexCellSide coordinate HexSouthEast
       ]

hexCellBoundarySide :: HexCoord -> HexDirection -> HexBoundarySide
hexCellBoundarySide coordinate direction =
  let (fromCorner, toCorner) = boundaryDirectionCorners direction
      from = hexCellVertex coordinate fromCorner
      to = hexCellVertex coordinate toCorner
   in HexBoundarySide direction from to (canonicalSide from to)
{-# INLINE hexCellBoundarySide #-}

hexCellBoundary :: HexCoord -> NonEmpty HexBoundarySide
hexCellBoundary coordinate =
  hexCellBoundarySide coordinate HexEast
    :| [ hexCellBoundarySide coordinate HexSouthEast
       , hexCellBoundarySide coordinate HexSouthWest
       , hexCellBoundarySide coordinate HexWest
       , hexCellBoundarySide coordinate HexNorthWest
       , hexCellBoundarySide coordinate HexNorthEast
       ]

cornerOffset :: HexCorner -> (Integer, Integer)
cornerOffset corner = case corner of
  HexEastCorner -> (2, 0)
  HexNorthEastCorner -> (1, 1)
  HexNorthWestCorner -> (-1, 1)
  HexWestCorner -> (-2, 0)
  HexSouthWestCorner -> (-1, -1)
  HexSouthEastCorner -> (1, -1)
{-# INLINE cornerOffset #-}

directionCorners :: HexDirection -> (HexCorner, HexCorner)
directionCorners direction = case direction of
  HexEast -> (HexEastCorner, HexNorthEastCorner)
  HexNorthEast -> (HexSouthEastCorner, HexEastCorner)
  HexNorthWest -> (HexSouthWestCorner, HexSouthEastCorner)
  HexWest -> (HexWestCorner, HexSouthWestCorner)
  HexSouthWest -> (HexNorthWestCorner, HexWestCorner)
  HexSouthEast -> (HexNorthEastCorner, HexNorthWestCorner)
{-# INLINE directionCorners #-}

boundaryDirectionCorners :: HexDirection -> (HexCorner, HexCorner)
boundaryDirectionCorners direction = case direction of
  HexEast -> (HexEastCorner, HexNorthEastCorner)
  HexSouthEast -> (HexNorthEastCorner, HexNorthWestCorner)
  HexSouthWest -> (HexNorthWestCorner, HexWestCorner)
  HexWest -> (HexWestCorner, HexSouthWestCorner)
  HexNorthWest -> (HexSouthWestCorner, HexSouthEastCorner)
  HexNorthEast -> (HexSouthEastCorner, HexEastCorner)
{-# INLINE boundaryDirectionCorners #-}

canonicalSide :: HexVertex -> HexVertex -> HexSide
canonicalSide left right =
  if left <= right then HexSide left right else HexSide right left
{-# INLINE canonicalSide #-}