packages feed

pure-cdt-0.2.0.0: app/Examples/Solenoid.hs

module Examples.Solenoid (
  drawSolenoid,
  drawRefinedSolenoid,
) where

import Data.List.NonEmpty (NonEmpty (..))
import Drawer (drawTriangulation)
import Examples.Common (canvas, orFail)
import Triangulation (
  Point (..),
  Polygon (..),
  Quality (..),
  constrainedTriangulate,
  defaultQuality,
  refine,
  vertices,
 )
import Triangulation.Generator (Rectangle (..), generatePointsWithDistance)

drawSolenoid :: FilePath -> IO ()
drawSolenoid path = do
  triangles <- orFail $ constrainedTriangulate outer [innerLeft, innerRight] points
  drawTriangulation path triangles [] [outer, innerLeft, innerRight]
  where
    outer = rectangleOf Rectangle {minCorner = Point 100 200, maxCorner = Point 1900 1800} (30, 20)
    innerLeft = rectangleOf Rectangle {minCorner = Point 300 400, maxCorner = Point 900 1600} (10, 20)
    innerRight = rectangleOf Rectangle {minCorner = Point 1100 400, maxCorner = Point 1700 1600} (10, 20)
    points = generatePointsWithDistance 1 4000 20 canvas (concatMap vertices [outer, innerLeft, innerRight])

-- | The boundary of a rectangle as a clockwise polygon, with @numH@ extra
-- points on each horizontal side and @numV@ on each vertical side.
rectangleOf :: Rectangle -> (Int, Int) -> Polygon
rectangleOf Rectangle {minCorner = Point x1 y1, maxCorner = Point x2 y2} (numH, numV) =
  let lowerLeft = Point x1 y1
      lowerRight = Point x2 y1
      upperRight = Point x2 y2
      upperLeft = Point x1 y2
      sideRight = pointsBetween upperRight lowerRight numV ++ [lowerRight]
      sideBottom = pointsBetween lowerRight lowerLeft numH ++ [lowerLeft]
      sideLeft = pointsBetween lowerLeft upperLeft numV ++ [upperLeft]
      sideTop = pointsBetween upperLeft upperRight numH
   in Polygon (upperRight :| (sideRight ++ sideBottom ++ sideLeft ++ sideTop))

-- | @n@ equally spaced points strictly between two points.
pointsBetween :: Point -> Point -> Int -> [Point]
pointsBetween (Point x1 y1) (Point x2 y2) n =
  let dx = (x2 - x1) / fromIntegral (n + 1)
      dy = (y2 - y1) / fromIntegral (n + 1)
   in [Point (x1 + dx * fromIntegral i) (y1 + dy * fromIntegral i) | i <- [1 .. n]]

-- | The same section, meshed for finite elements: the polygons with only a
-- few points on each side, refined to a 28° angle bound and a maximum
-- triangle area, so that the density comes from the refinement rather than
-- from random points.
drawRefinedSolenoid :: FilePath -> IO ()
drawRefinedSolenoid path = do
  coarse <- orFail $ constrainedTriangulate outer [innerLeft, innerRight] []
  triangles <- orFail $ refine defaultQuality {minAngle = 28, maxArea = Just 1500} coarse
  drawTriangulation path triangles [] [outer, innerLeft, innerRight]
  where
    outer = rectangleOf Rectangle {minCorner = Point 100 200, maxCorner = Point 1900 1800} (3, 2)
    innerLeft = rectangleOf Rectangle {minCorner = Point 300 400, maxCorner = Point 900 1600} (1, 2)
    innerRight = rectangleOf Rectangle {minCorner = Point 1100 400, maxCorner = Point 1700 1600} (1, 2)