packages feed

keid-render-basic-0.1.3.0: src/Render/Lit/Material/Collect.hs

module Render.Lit.Material.Collect
  ( LoadedModel
  , SceneModel(..)
  , sceneMaterials
  , modelMaterials
  , nodeMaterials
  ) where

import RIO

import RIO.Map qualified as Map
import RIO.Set qualified as Set
import RIO.Vector.Storable qualified as Storable

import Render.Lit.Material (shiftTextures)
import Render.Lit.Material.Model qualified as LitMaterial
import Resource.Buffer qualified as Buffer
import Resource.Mesh.Types qualified as Mesh
import Resource.Mesh.Lit qualified as Lit
import Render.Lit.Material (Material)

type LoadedModel =
  ( Mesh.Meta
  , Storable.Vector Lit.MaterialNode
  , LitMaterial.Model 'Buffer.Staged
  )

data SceneModel models textures = SceneModel
  { smLabel            :: Text
  , smGetModel         :: models -> LoadedModel
  , smGetTextureOffset :: textures -> Int32
  }

sceneMaterials
  :: Foldable t
  => models
  -> textures
  -> t (SceneModel models textures)
  -> Storable.Vector Material
sceneMaterials loadedModels combinedTextures =
  Storable.fromList . map snd . Map.elems . snd . foldl' beep (mempty, mempty)
  where
    beep acc SceneModel{..} =
      let
        (_meta, materialNodes, _model) = smGetModel loadedModels
        textureOffset = smGetTextureOffset combinedTextures
      in
        modelMaterials smLabel textureOffset acc (Storable.toList materialNodes)

modelMaterials
  :: Foldable t
  => Text
  -> Int32
  -> (Map Text (Set Int), Map Int (Text, Material))
  -> t Lit.MaterialNode
  -> (Map Text (Set Int), Map Int (Text, Material))
modelMaterials label textureOffset =
  foldr (nodeMaterials label textureOffset)

nodeMaterials
  :: Text
  -> Int32
  -> Lit.MaterialNode
  -> (Map Text (Set Int), Map Int (Text, Material))
  -> (Map Text (Set Int), Map Int (Text, Material))
nodeMaterials label textureStart Lit.MaterialNode{mnMaterialIx, mnMaterial} (ids, collection) =
  case Map.lookup mnMaterialIx collection of
    Nothing ->
      ( Map.insertWith mappend label (Set.singleton mnMaterialIx) ids
      , Map.insert mnMaterialIx (label, newMaterial) collection
      )
    Just (oldLabel, oldMaterial) ->
      if newMaterial /= oldMaterial then
        error $ unlines
          [ "Ouf... The material indices are clashing for " <> show label
          , "ID: " <> show mnMaterialIx <> " from " <> show oldLabel
          , ""
          , "Old: " <> show oldMaterial
          , "Now: " <> show mnMaterial
          , ""
          , "Bad material offset? Known material IDs:"
          , unlines do
              (knownLabel, knownIds) <- Map.toList ids
              pure $ show knownLabel <> ": " <> show (Set.toList knownIds)
          , "Try setting " <> show label <> " material offset to " <> show suggestOffset
          ]
      else
        (ids, collection)
  where
    newMaterial =
      shiftTextures textureStart mnMaterial

    suggestOffset :: Int
    suggestOffset =
      (+ 1) . foldr max 0 $
        catMaybes . map Set.lookupMax $
          Map.elems (Map.delete label ids)