packages feed

moonlight-triangulation-1.4.0.2: bench/spade-compare/hs/Moonlight/Triangulation/Bench/SpadeCompare/Intersection.hs

{-# LANGUAGE DataKinds #-}

-- | Corridor-walk lanes, their inputs, and their gates.
module Moonlight.Triangulation.Bench.SpadeCompare.Intersection where

import Data.List (sortOn)
import Data.Word (Word64)
import System.FilePath ((</>))
import qualified Data.Vector as V
import Moonlight.Triangulation
import Moonlight.Triangulation.IntersectionIterator
import Moonlight.Triangulation.Bench.SpadeCompare.Support
import Moonlight.Triangulation.Bench.SpadeCompare.Input (randomPoints)

-- | Query endpoints pulled inward. The generator fills [-1, 1]^2, so a point
-- with both coordinates inside [-0.5, 0.5] is strictly inside the hull of a
-- few thousand of them and locating it lands on a face. A chord that starts
-- OUTSIDE the hull takes a different route on each side — spade walks the hull
-- from the located hull edge while this side falls back to a scan over every
-- element — so those chords are the "intersection-outside" cliff lane and are
-- kept out of this one deliberately.
interiorQueryPoints :: Word64 -> Int -> [Point]
interiorQueryPoints seed count =
  [Point (0.5 * x) (0.5 * y) | Point x y <- randomPoints seed count]

-- | Query origins pushed well clear of the hull, so locating one is certain to
-- report the outside of the convex hull rather than a face.
exteriorQueryPoints :: Word64 -> Int -> [Point]
exteriorQueryPoints seed count =
  [Point (4 * x) (4 * y) | Point x y <- randomPoints seed count]

intersectionChords :: Int -> [(Point, Point)]
intersectionChords queryCount =
  zip
    (interiorQueryPoints 0x3141592653589793 queryCount)
    (interiorQueryPoints 0x2718281828459045 queryCount)

outsideChords :: Int -> [(Point, Point)]
outsideChords queryCount =
  zip
    (exteriorQueryPoints 0x243f6a8885a308d3 queryCount)
    (interiorQueryPoints 0x2718281828459045 queryCount)

-- | One line per query: the line's own endpoints, then its intersection
-- sequence in the order the walk reports it. Every branch of the walk turns on
-- an orientation predicate over a mesh that already agrees bit for bit, so the
-- whole sequence is exactly determined and crosses implementations — order
-- included, which is why nothing here is sorted.
writeIntersectionGate :: FilePath -> Int -> Int -> IO ()
writeIntersectionGate directory pointCount queryCount = do
  triangulation <- delaunayOf pointCount
  chords <- requireQueryChords (intersectionChords queryCount)
  writeFile
    (directory </> ("intersection-" <> show pointCount <> "-" <> show queryCount <> "-chords.txt"))
    (unlines (map (intersectionLine triangulation) chords))

writeIntersectionOutsideGate :: FilePath -> Int -> Int -> IO ()
writeIntersectionOutsideGate directory pointCount queryCount = do
  triangulation <- delaunayOf pointCount
  chords <- requireQueryChords (outsideChords queryCount)
  writeFile
    ( directory
        </> ("intersection-outside-" <> show pointCount <> "-" <> show queryCount <> ".txt")
    )
    (unlines (map (intersectionLine triangulation) chords))

-- | Lines whose endpoints are mesh vertices, which is the only way to reach the
-- vertex and overlap branches of the walk at all: over generated doubles no
-- chord lands exactly on a site, so a gate built from chords alone exercises
-- one branch of three. The first family joins vertices the constraint
-- generator's index pairs select, which starts and ends the walk on a vertex;
-- the second runs along an existing edge, which is the only shape that produces
-- an overlap. Both name their endpoints by coordinate, the second from a list
-- both sides sort by the same encoding, because vertex handles do not cross.
writeIntersectionVertexGate :: FilePath -> Int -> IO ()
writeIntersectionVertexGate directory pointCount = do
  triangulation <- delaunayOf pointCount
  let sites = V.fromList (randomPoints 0x9e3779b97f4a7c15 pointCount)
      joins =
        [ (sites V.! fromIndex, sites V.! toIndex)
        | (fromIndex, toIndex) <- constraintPairs pointCount intersectionVertexLines
        ]
      alongEdges = take intersectionVertexLines (sortedEdgeEndpoints triangulation)
  queryLines <- requireQueryChords (joins <> alongEdges)
  writeFile
    (directory </> ("intersection-vertexlines-" <> show pointCount <> ".txt"))
    (unlines (map (intersectionLine triangulation) queryLines))

intersectionVertexLines :: Int
intersectionVertexLines = 100

sortedEdgeEndpoints
  :: DelaunayTriangulation Point
  -> [(Point, Point)]
sortedEdgeEndpoints triangulation =
  map snd
    ( sortOn
        fst
        [ (coordHex first <> coordHex second, (first, second))
        | edge <- undirectedEdges triangulation
        , let (from, to) = undirectedEndpoints triangulation edge
              p = vertexPoint triangulation from
              q = vertexPoint triangulation to
              (first, second) = if pointKey p <= pointKey q then (p, q) else (q, p)
        ]
    )
 where
  pointKey (Point x y) = (x, y)

intersectionLine
  :: DelaunayTriangulation Point
  -> (QueryPoint, QueryPoint)
  -> String
intersectionLine triangulation (from, to) =
  coordHex (queryPointValue from)
    <> coordHex (queryPointValue to)
    <> concatMap (encodeIntersection triangulation) (lineIntersections triangulation from to)

encodeIntersection
  :: DelaunayTriangulation Point
  -> Intersection
  -> String
encodeIntersection triangulation event = case event of
  VertexIntersection vertex -> "V" <> coordHex (vertexPoint triangulation vertex)
  EdgeIntersection edge -> "E" <> directedHex edge
  EdgeOverlap edge -> "O" <> directedHex edge
 where
  directedHex edge =
    coordHex (vertexPoint triangulation (origin triangulation edge))
      <> coordHex (vertexPoint triangulation (destination triangulation edge))

-- | The corridor walk, counted. 'lineIntersections' materializes its answer, so
-- forcing the spine forces every step that produced it; spade's iterator is
-- lazy and is consumed by the same count.
intersectionWalk
  :: DelaunayTriangulation Point
  -> [(QueryPoint, QueryPoint)]
  -> Int
intersectionWalk triangulation =
  foldl' (\total (from, to) -> total + length (lineIntersections triangulation from to)) 0