packages feed

keid-geometry-0.1.0.1: src/Geometry/Icosphere.hs

module Geometry.Icosphere
  ( generateIndexed

  , icofaces
  , icopoints

  , icosphere
  , icotris_v1
  , subNormal
  , subdivide
  ) where

import RIO

import Data.List (iterate, (!!))
import Geomancy.Vec3 (Vec3, vec3)
import Geomancy.Vec3 qualified as Vec3
import Resource.Model qualified as Model
import Data.Vector qualified as Vector
import Data.Vector.Mutable qualified as Mutable
import RIO.Vector.Partial ((!))
import RIO.Map qualified as Map
import RIO.Vector.Storable qualified as Storable
import Control.Monad.State.Strict (get, put, runState)
import Vulkan.NamedType ((:::))

import Geometry.Face (Face(..))

generateIndexed
  :: ( Fractional scale
     , Storable pos
     , Storable vertexAttr
     )
  => "subdivisions"  ::: Natural
  -> "initial"       ::: (Vec3 -> pointAttr)
  -> "midpoint"      ::: (scale -> Vec3 -> pointAttr -> pointAttr -> pointAttr)
  -> "vertex"        ::: (Vector (Vec3, pointAttr) -> [Face Int] -> Vector (pos, vertexAttr))
  -> "model vectors" ::: (Storable.Vector pos, Storable.Vector vertexAttr, Storable.Vector Word32)
generateIndexed details mkInitialAttrs mkMidpointAttrs mkVertices =
  ( Storable.convert pv
  , Storable.convert av
  , Storable.fromList iv
  )
  where
    (pv, av) = Vector.unzip $ mkVertices finalPoints faces

    iv = do
      face <- faces
      vert <- toList face
      pure $ fromIntegral vert

    (faces, (_midpoints, finalPoints, _finalPointsCount)) =
      runState
        (go icofaces details details)
        ( mempty
        , initialPoints
        , Vector.length icopoints
        )

    maxPoints = length icofaces * (4 ^ details) - 8

    initialPoints = Vector.create do
      v <- Mutable.new maxPoints
      Vector.imapM_
        (Mutable.unsafeWrite v)
        (Vector.map (id &&& mkInitialAttrs) icopoints)
      pure v

    go curFaces maxLevel curLevel = do
      -- traceShowM $ "Inflating level " <> textShow (maxLevel - curLevel)
      case curLevel of
        0 ->
          pure curFaces
        _ -> do
          let scale = fromIntegral curLevel / fromIntegral maxLevel
          next <- traverse (subdivideFace scale) curFaces
          go (mconcat next) maxLevel (curLevel - 1)

    subdivideFace scale (Face a b c) = do
      (mids, points, numPoints) <- get

      let
        extras = mempty
        (midsAB, extrasAB, ab) = midpoint scale mids   extras   points numPoints (a, b)
        (midsBC, extrasBC, bc) = midpoint scale midsAB extrasAB points numPoints (b, c)
        (midsCA, extrasCA, ca) = midpoint scale midsBC extrasBC points numPoints (c, a)

      put
        ( midsCA
        , runST do
            old <- Vector.unsafeThaw points

            Vector.imapM_
              ( \i point ->
                  Mutable.unsafeWrite old (numPoints + i) point
              )
              extrasCA

            Vector.unsafeFreeze old
        , numPoints + Vector.length extrasCA
        )

      pure
        [ Face ab bc ca
        , Face ca a ab
        , Face ab b bc
        , Face bc c ca
        ]

    midpoint scale mids extras points numPoints parents =
      case Map.lookup parents mids of
        Just knownIx ->
          ( mids
          , extras
          , knownIx
          )
        Nothing ->
          let
            (pos1, attr1) = points ! fst parents
            (pos2, attr2) = points ! snd parents
            midPos = Vec3.lerp 0.5 pos1 pos2

            newIx =
              numPoints +
              Vector.length extras

            point =
              ( midPos
              , mkMidpointAttrs scale midPos attr1 attr2
              )
          in
            ( Map.insert parents newIx mids
            , Vector.snoc extras point
            , newIx
            )

icofaces :: [Face Int]
icofaces =
  [ -- faces around point 0
    Face  5 11 0
  , Face  1  5 0
  , Face  7  1 0
  , Face 10  7 0
  , Face 11 10 0

    -- 5 adjacent faces
  , Face 9  5  1
  , Face 4 11  5
  , Face 2 10 11
  , Face 6  7 10
  , Face 8  1  7

    -- 5 adjacent faces around point 3
  , Face 4 9 3
  , Face 2 4 3
  , Face 6 2 3
  , Face 8 6 3
  , Face 9 8 3

    -- 5 adjacent faces
  , Face  5 9 4
  , Face 11 4 2
  , Face 10 2 6
  , Face  7 6 8
  , Face  1 8 9
  ]

icopoints :: Vector Vec3
icopoints = Vector.fromList
  [ vec3 (-1) 0   t
  , vec3   1  0   t
  , vec3 (-1) 0 (-t)
  , vec3   1  0 (-t)

  , vec3   0 (-t) (-1)
  , vec3   0 (-t)   1
  , vec3   0   t  (-1)
  , vec3   0   t    1

  , vec3   t     1  0
  , vec3   t   (-1) 0
  , vec3 (-t)    1  0
  , vec3 (-t)  (-1) 0
  ]
  where
    t = (1.0 + sqrt 5.0) / 2.0

icotris_v1 :: [[Vec3]]
icotris_v1 =
  [ -- faces around point 0
    [ icopoints ! 0
    , icopoints ! 11
    , icopoints ! 5
    ]

  , [ icopoints ! 0
    , icopoints ! 5
    , icopoints ! 1
    ]

  , [ icopoints ! 0
    , icopoints ! 1
    , icopoints ! 7
    ]
{-
  , [ icopoints ! 0
    , icopoints ! 1
    , icopoints ! 7
    ]
-}
  , [ icopoints ! 0
    , icopoints ! 7
    , icopoints ! 10
    ]

  , [ icopoints ! 0
    , icopoints ! 10
    , icopoints ! 11
    ]

    -- 5 adjacent faces

  , [ icopoints ! 1
    , icopoints ! 5
    , icopoints ! 9
    ]

  , [ icopoints ! 5
    , icopoints ! 11
    , icopoints ! 4
    ]

  , [ icopoints ! 11
    , icopoints ! 10
    , icopoints ! 2
    ]

  , [ icopoints ! 10
    , icopoints ! 7
    , icopoints ! 6
    ]

  , [ icopoints ! 7
    , icopoints ! 1
    , icopoints ! 8
    ]

    -- 5 adjacent faces around point 3

  , [ icopoints ! 3
    , icopoints ! 9
    , icopoints ! 4
    ]

  , [ icopoints ! 3
    , icopoints ! 4
    , icopoints ! 2
    ]

  , [ icopoints ! 3
    , icopoints ! 2
    , icopoints ! 6
    ]

  , [ icopoints ! 3
    , icopoints ! 6
    , icopoints ! 8
    ]

  , [ icopoints ! 3
    , icopoints ! 8
    , icopoints ! 9
    ]

    -- 5 adjacent faces

  , [ icopoints ! 4
    , icopoints ! 9
    , icopoints ! 5
    ]

  , [ icopoints ! 2
    , icopoints ! 4
    , icopoints ! 11
    ]

  , [ icopoints ! 6
    , icopoints ! 2
    , icopoints ! 10
    ]

  , [ icopoints ! 8
    , icopoints ! 6
    , icopoints ! 7
    ]

  , [ icopoints ! 9
    , icopoints ! 8
    , icopoints ! 1
    ]
  ]

icosphere :: (Vec3 -> attrs) -> Int -> [Model.Vertex Vec3.Packed attrs]
icosphere mkAttrs n = do
  v <- reverse . concat $ subNormal n icotris_v1
  pure Model.Vertex
    { vPosition = Vec3.Packed v
    , vAttrs    = mkAttrs v
    }

subNormal :: Int -> [[Vec3]] -> [[Vec3]]
subNormal nu tris = map (map Vec3.normalize) $ subdivide nu tris

subdivide :: Int -> [[Vec3]] -> [[Vec3]]
subdivide frequency tris =
  iterate (concatMap subdivideTri) tris !! (frequency - 1)
  where
    subdivideTri = \case
      [v1, v2, v3] ->
        let
          a = midpoint v1 v2
          b = midpoint v2 v3
          c = midpoint v3 v1
        in
          [ [ v1, a, c ]
          , [ v2, b, a ]
          , [ v3, c, b ]
          , [  a, b, c ]
          ]
      _ ->
        error "subdivideTri: not a triangle somehow"

    midpoint a b = (a + b) / 2