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