packages feed

moonlight-triangulation-1.0.0.0: src-dcel/Moonlight/Triangulation/PointLocation.hs

{-# LANGUAGE BangPatterns #-}

-- | Walk location over the finite mesh; the hinted form takes a starting point
-- and reports the steps it spent.
module Moonlight.Triangulation.PointLocation
  ( locatePoint
  , locatePointWithHint
  ) where

import Data.List (find)
import Moonlight.Triangulation.Dcel
import Moonlight.Triangulation.Handles.HandleDefs
import Moonlight.Triangulation.Handles.Iterators.FixedIterators
import Moonlight.Triangulation.Internal.FaceProbe
import Moonlight.Triangulation.Math
import Moonlight.Triangulation.Types

-- | Locate an admitted point without an initial topology hint.
locatePoint :: Triangulation mode vertex directed undirected face -> QueryPoint -> Location
locatePoint triangulation query = fst (locatePointWithHint triangulation Nothing query)

-- | Locate an admitted point from an optional starting cell and report the
-- descent work.
locatePointWithHint :: Triangulation mode vertex directed undirected face -> Maybe LocationHint -> QueryPoint -> (Location, LocationStats)
locatePointWithHint triangulation hint queryPoint
  | numVertices triangulation == 0 = (EmptyTriangulation, emptyLocationStats)
  | numInnerFaces triangulation == 0 = (locateDegenerate triangulation query, emptyLocationStats)
  | otherwise = walk budget 0 startFace
 where
  !query = queryPointValue queryPoint
  !budget = max 8 (numFaces triangulation + numUndirectedEdges triangulation + 4)
  !startFace = chooseStart triangulation hint

  walk !remaining !steps !face
    | remaining <= 0 = scanAll steps
    | face == outerFace =
        (outsideLocation triangulation query, LocationStats steps False)
    | otherwise =
        case probeFace triangulation face query of
          FaceHit location -> (location, LocationStats (steps + 1) False)
          FaceCross edge ->
            let adjacent = incidentFace triangulation edge
             in if adjacent == outerFace
                  then (OutsideConvexHull (Just edge), LocationStats (steps + 1) False)
                  else walk (remaining - 1) (steps + 1) adjacent
          FaceMiss -> scanAll (steps + 1)

  scanAll !steps =
    case findHit (innerFaces triangulation) of
      Just location -> (location, LocationStats steps True)
      Nothing -> (outsideLocation triangulation query, LocationStats steps True)

  findHit [] = Nothing
  findHit (face : remaining) =
    case probeFace triangulation face query of
      FaceHit location -> Just location
      _ -> findHit remaining

data FaceProbe = FaceHit !Location | FaceCross !DirectedEdgeId | FaceMiss

probeFace :: Triangulation mode vertex directed undirected face -> FaceId -> Point -> FaceProbe
probeFace triangulation face query =
  case innerFaceDirectedEdges triangulation face of
    Nothing -> FaceMiss
    Just (e0, e1, e2) -> classify [e0, e1, e2] Nothing
 where
  classify [] Nothing = FaceHit (InFace face)
  classify [] (Just edge) = FaceCross edge
  classify (edge : remaining) crossing =
    let fromVertex = origin triangulation edge
        toVertex = destination triangulation edge
        from = vertexPoint triangulation fromVertex
        to = vertexPoint triangulation toVertex
     in case probeBoundary reverseEdge query edge fromVertex from toVertex to of
          BoundaryClear -> classify remaining crossing
          BoundaryOnVertex vertex -> FaceHit (OnVertex vertex)
          BoundaryOnEdge boundary -> FaceHit (OnEdge boundary)
          BoundaryCrossing boundary -> classify remaining (Just boundary)

locateDegenerate :: Triangulation mode vertex directed undirected face -> Point -> Location
locateDegenerate triangulation query =
  case find ((== query) . vertexPoint triangulation) (vertices triangulation) of
    Just vertex -> OnVertex vertex
    Nothing ->
      case find contains (undirectedEdges triangulation) of
        Just edge -> OnEdge (normalizedDirected edge)
        Nothing ->
          case undirectedEdges triangulation of
            [] -> OutsideConvexHull Nothing
            edge : _ ->
              let forward = normalizedDirected edge
                  from = vertexPoint triangulation (origin triangulation forward)
                  to = vertexPoint triangulation (destination triangulation forward)
                  directed = case orient2d from to query of
                    GT -> forward
                    LT -> reverseEdge forward
                    EQ -> forward
               in OutsideConvexHull (Just directed)
 where
  contains edge =
    let forward = normalizedDirected edge
     in onClosedSegment
          (vertexPoint triangulation (origin triangulation forward))
          (vertexPoint triangulation (destination triangulation forward))
          query

outsideLocation :: Triangulation mode vertex directed undirected face -> Point -> Location
outsideLocation triangulation query =
  OutsideConvexHull (find visible (faceDirectedEdges triangulation outerFace))
 where
  visible edge =
    orient2d
      (vertexPoint triangulation (origin triangulation edge))
      (vertexPoint triangulation (destination triangulation edge))
      query
      /= LT

chooseStart :: Triangulation mode vertex directed undirected face -> Maybe LocationHint -> FaceId
chooseStart triangulation hint =
  case hint of
    Just (FaceHint face@(FaceId index))
      | index > 0 && fromIntegral index < numFaces triangulation -> face
    Just (VertexHint vertex@(VertexId index))
      | toInteger index < toInteger (numVertices triangulation) ->
          case [face | edge <- vertexOutgoingEdges triangulation vertex, let face = incidentFace triangulation edge, face /= outerFace] of
            face : _ -> face
            [] -> FaceId 1
      | otherwise -> FaceId 1
    _ -> FaceId 1