keid-render-basic-0.1.8.0: src/Render/Unlit/Sprite/Model.hs
{-# OPTIONS_GHC -fplugin Foreign.Storable.Generic.Plugin #-}
module Render.Unlit.Sprite.Model
( InstanceAttrs(..)
, StorableAttrs
, InstanceBuffer
, fromTexture
, fromAtlas
, animate_
) where
import RIO
import Engine.Vulkan.Format (HasVkFormat(..))
import Engine.Vulkan.Pipeline.Graphics (HasVertexInputBindings(..), instanceFormat)
import Foreign.Storable.Generic (GStorable)
import Geomancy (Vec2, Vec4, UVec2, vec2, vec4, withUVec2, withVec2)
import Geomancy.Vec4 qualified as Vec4
import Render.Samplers qualified as Samplers
import Resource.Buffer qualified as Buffer
import Resource.Image.Atlas (Atlas)
import Resource.Image.Atlas qualified as Atlas
import RIO.Vector.Storable qualified as Storable
import Vulkan.Core10 qualified as Vk
import Vulkan.NamedType ((:::))
import Vulkan.Zero (Zero(..))
data InstanceAttrs = InstanceAttrs
{ vertRect :: Vec4
, fragRect :: Vec4
, tint :: Vec4
, outline :: Vec4
, animation :: Vec4 -- direction xy, number of frames, speed
, textureSize :: UVec2
, samplerId :: Int32
, textureId :: Int32
}
deriving (Show, Generic)
-- XXX: okay, the layout matches
instance GStorable InstanceAttrs
instance Zero InstanceAttrs where
zero = InstanceAttrs
{ vertRect = 0
, fragRect = 0
, tint = 0
, outline = 0
, animation = 0
, textureSize = 0
, samplerId = 0
, textureId = 0
}
instance HasVkFormat InstanceAttrs where
getVkFormat =
[ Vk.FORMAT_R32G32B32A32_SFLOAT -- Quad scale+offset
, Vk.FORMAT_R32G32B32A32_SFLOAT -- UV scale+offset
, Vk.FORMAT_R32G32B32A32_SFLOAT -- Tint
, Vk.FORMAT_R32G32B32A32_SFLOAT -- Outline (when enabled)
, Vk.FORMAT_R32G32B32A32_SFLOAT -- Linear animation direction/duration, num frames, phase
, Vk.FORMAT_R32G32_UINT -- Texture size
, Vk.FORMAT_R32G32_SINT -- Sampler + texture IDs
]
instance HasVertexInputBindings InstanceAttrs where
vertexInputBindings = [instanceFormat @InstanceAttrs]
type StorableAttrs = Storable.Vector InstanceAttrs
type InstanceBuffer stage = Buffer.Allocated stage InstanceAttrs
fromTexture
:: Int32 -- ^ Sampler index.
-> Int32 -- ^ Texture index.
-> Vec2 -- ^ Sprite size.
-> Vec2 -- ^ Sprite position.
-> InstanceAttrs
fromTexture sampler texture wh pos =
InstanceAttrs
{ vertRect = Vec4.fromVec22 pos wh
, fragRect = Vec4.fromVec22 0 1
, tint = 1
, outline = 0
, animation = 0
, textureSize = 0
, samplerId = sampler
, textureId = texture
}
fromAtlas
:: Int32 -- ^ Texture ID.
-> Atlas
-> Vec2 -- ^ Sprite scale, wrt. to native tile size
-> Vec2 -- ^ Tile position in atlas tiles. Can be fractional when using subgrids.
-> Vec2 -- ^ Sprite position.
-> InstanceAttrs
fromAtlas texture atlas scale atlasPos worldPos =
InstanceAttrs
{ vertRect = Vec4.fromVec22 worldPos (tileSize * scale)
, fragRect = Vec4.fromVec22 ((atlasPos + tileMargins / tileSize) * uvScale) uvScale
, tint = 1
, outline = 0
, animation = 0
, textureSize = Atlas.sizePx atlas
, samplerId = Samplers.nearest Samplers.indices
, textureId = texture
}
where
uvScale = Atlas.uvScale atlas
tileSize =
withUVec2 (Atlas.tileSizePx atlas) \tw th ->
vec2 (fromIntegral tw) (fromIntegral th)
tileMargins =
withUVec2 (Atlas.marginPx atlas) \mx my ->
withVec2 atlasPos \px py ->
vec2 (fromIntegral mx * px) (fromIntegral my * py)
-- | Simple animation controller with left-to-right linear cycle.
animate_
:: "margin" ::: Float
-> "num.frames" ::: Int
-> "frame duration" ::: Float
-> "phase" ::: Float
-> InstanceAttrs
-> InstanceAttrs
animate_ margin numFrames frameDuration phase attrs = attrs
{ animation =
vec4
(fromIntegral numFrames)
frameDuration
margin
phase
}