packages feed

moonlight-planar-1.1.0.0: src-public/Moonlight/Planar/Internal/PowerDiagram/Locality.hs

{-# LANGUAGE BangPatterns #-}

-- | The discardable hidden-support index and exact face walk for one regular section.
module Moonlight.Planar.Internal.PowerDiagram.Locality where

import qualified Data.Foldable as Foldable
import qualified Data.List as List
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map)
import qualified Data.Set as Set
import Data.Set (Set)
import Moonlight.Planar.Exact
  ( ExactPoint
  , exactOrient2d
  , exactPoint
  )
import Moonlight.Planar.Internal.BoundaryCycle (orderedPair)
import Moonlight.Planar.Internal.ExactRational (ExactRational)
import Moonlight.Planar.Internal.PowerDiagram.Generator
  ( ExactPowerGenerator
  )
import Moonlight.Planar.Internal.PowerDiagram.Model

patchRegularLocality
  :: Ord label
  => RegularLocality label
  -> Set (RegularFaceKey label)
  -> Set label
  -> RegularSection label
  -> Either (LocalRegularEditObstruction label) (RegularLocality label)
patchRegularLocality locality removedFaces hiddenToAttach section = do
  seed <-
    case Map.lookupMin (sectionRegularFaces section) of
      Nothing -> Left LocalRegularLocalityUnavailable
      Just (minimumFace, _) ->
        Right
          ( if Map.member (localityFaceSeed locality) (sectionRegularFaces section)
              then localityFaceSeed locality
              else minimumFace
          )
  let detachedForFaces =
        Set.unions
          [ Map.findWithDefault Set.empty faceKey (localitySupportHidden locality)
          | faceKey <- Set.toAscList removedFaces
          ]
      detachedLabels = Set.union detachedForFaces hiddenToAttach
      detached = Set.foldl' (flip detachRegularHidden) locality detachedLabels
  attachments <-
    traverse
      (locateHiddenSupport seed section)
      (Set.toAscList hiddenToAttach)
  pure
    ( Foldable.foldl'
        (\current (label, faceKey) -> attachRegularHidden label faceKey current)
        detached{localityFaceSeed = seed}
        attachments
    )

locateHiddenSupport
  :: Ord label
  => RegularFaceKey label
  -> RegularSection label
  -> label
  -> Either (LocalRegularEditObstruction label) (label, RegularFaceKey label)
locateHiddenSupport seed section label = do
  generator <- requireGeneratorFromMap (sectionGenerators section) label
  location <-
    maybe (Left LocalRegularSlopeLocationFailed) Right
      (locateRegularSlope seed section generator)
  case location of
    RegularSlopeInside faceKey -> Right (label, faceKey)
    RegularSlopeOutside edgeKey -> Left (LocalRegularSlopeOutside edgeKey)

attachRegularHidden
  :: Ord label
  => label
  -> RegularFaceKey label
  -> RegularLocality label
  -> RegularLocality label
attachRegularHidden label faceKey locality =
  locality
    { localityHiddenSupport = Map.insert label faceKey (localityHiddenSupport locality)
    , localitySupportHidden =
        Map.insertWith Set.union faceKey (Set.singleton label) (localitySupportHidden locality)
    }

detachRegularHidden
  :: Ord label
  => label
  -> RegularLocality label
  -> RegularLocality label
detachRegularHidden label locality =
  case Map.lookup label (localityHiddenSupport locality) of
    Nothing -> locality
    Just faceKey ->
      locality
        { localityHiddenSupport = Map.delete label (localityHiddenSupport locality)
        , localitySupportHidden =
            Map.update
              (\labels ->
                 let retained = Set.delete label labels
                  in if Set.null retained then Nothing else Just retained)
              faceKey
              (localitySupportHidden locality)
        }

buildRegularLocality
  :: Ord label
  => RegularSection label
  -> Maybe (RegularLocality label)
buildRegularLocality section = do
  (seed, _) <- Map.lookupMin (sectionRegularFaces section)
  Foldable.foldlM
    (attachHiddenGenerator section)
    RegularLocality
      { localityFaceSeed = seed
      , localityHiddenSupport = Map.empty
      , localitySupportHidden = Map.empty
      }
    [ label
    | (_, label) <- Map.toAscList (sectionSlopeRepresentatives section)
    , Map.lookup label (sectionRegularDispositions section)
        == Just RegularSiteHidden
    ]

attachHiddenGenerator
  :: Ord label
  => RegularSection label
  -> RegularLocality label
  -> label
  -> Maybe (RegularLocality label)
attachHiddenGenerator section locality label = do
  generator <- Map.lookup label (sectionGenerators section)
  location <- locateRegularSlope (localityFaceSeed locality) section generator
  case location of
    RegularSlopeInside faceKey ->
      Just
        (attachRegularHidden label faceKey locality)
          { localityFaceSeed = faceKey
          }
    RegularSlopeOutside _ -> Nothing

locateRegularSlope
  :: Ord label
  => RegularFaceKey label
  -> RegularSection label
  -> ExactPowerGenerator label
  -> Maybe (RegularSlopeLocation label)
locateRegularSlope seed section generator =
  descendRegularSlope
    (exactPoint queryX queryY)
    section
    (Map.size (sectionRegularFaces section) + 1)
    seed
 where
  (queryX, queryY) = exactGeneratorSlope generator

descendRegularSlope
  :: Ord label
  => ExactPoint
  -> RegularSection label
  -> Int
  -> RegularFaceKey label
  -> Maybe (RegularSlopeLocation label)
descendRegularSlope query section !remaining faceKey
  | remaining <= 0 = Nothing
  | otherwise = do
      face <- Map.lookup faceKey (sectionRegularFaces section)
      case firstExteriorFaceEdge query section face of
        Nothing -> Just (RegularSlopeInside faceKey)
        Just edgeKey -> do
          edgeSection <- Map.lookup edgeKey (sectionRegularEdges section)
          case Set.lookupMin (Set.delete faceKey (sectionRegularEdgeFaces edgeSection)) of
            Just adjacent ->
              descendRegularSlope query section (remaining - 1) adjacent
            Nothing -> Just (RegularSlopeOutside edgeKey)

firstExteriorFaceEdge
  :: Ord label
  => ExactPoint
  -> RegularSection label
  -> RegularFace label
  -> Maybe (RegularEdgeKey label)
firstExteriorFaceEdge query section face =
  fmap
    (uncurry orderedPair)
    ( List.find
        (faceEdgeExcludes query section)
        (regularFaceDirectedEdgeKeys face)
    )

faceEdgeExcludes
  :: Ord label
  => ExactPoint
  -> RegularSection label
  -> RegularEdgeKey label
  -> Bool
faceEdgeExcludes query section (fromLabel, toLabel) =
  generatorEdgeExcludes
    query
    (sectionGenerators section)
    (fromLabel, toLabel)

generatorEdgeExcludes
  :: Ord label
  => ExactPoint
  -> Map label (ExactPowerGenerator label)
  -> RegularEdgeKey label
  -> Bool
generatorEdgeExcludes query generators (fromLabel, toLabel) =
  case (Map.lookup fromLabel generators, Map.lookup toLabel generators) of
    (Just fromGenerator, Just toGenerator) ->
      exactOrient2d
        (uncurry exactPoint (exactGeneratorSlope fromGenerator))
        (uncurry exactPoint (exactGeneratorSlope toGenerator))
        query
        == LT
    _ -> False

generatorEdgeExcludesScaled
  :: Ord label
  => ExactRational
  -> (ExactRational, ExactRational)
  -> Map label (ExactPowerGenerator label)
  -> RegularEdgeKey label
  -> Bool
generatorEdgeExcludesScaled scale (queryX, queryY) generators (fromLabel, toLabel) =
  case (Map.lookup fromLabel generators, Map.lookup toLabel generators) of
    (Just fromGenerator, Just toGenerator) ->
      let (fromX, fromY) = exactGeneratorSlope fromGenerator
          (toX, toY) = exactGeneratorSlope toGenerator
       in (toX - fromX) * (queryY - scale * fromY)
            - (toY - fromY) * (queryX - scale * fromX)
            < 0
    _ -> False