packages feed

aztecs-gl 0.2.1 → 0.3.0

raw patch · 15 files changed

+618/−610 lines, 15 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Aztecs.GL: render :: forall (m :: Type -> Type). MonadIO m => Access m ()
- Aztecs.GL.Image: Image :: Image PixelRGBA8 -> !TextureFilter -> Image
- Aztecs.GL.Image: Linear' :: TextureFilter
- Aztecs.GL.Image: Nearest :: TextureFilter
- Aztecs.GL.Image: [imageData] :: Image -> Image PixelRGBA8
- Aztecs.GL.Image: [imageFilter] :: Image -> !TextureFilter
- Aztecs.GL.Image: data Image
- Aztecs.GL.Image: data TextureFilter
- Aztecs.GL.Image: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.Image.Image
- Aztecs.GL.Image: loadImage :: FilePath -> IO Image
- Aztecs.GL.Image.Internal: ImageState :: !TextureObject -> !V2 Float -> ImageState
- Aztecs.GL.Image.Internal: [imageSize] :: ImageState -> !V2 Float
- Aztecs.GL.Image.Internal: [imageTexture] :: ImageState -> !TextureObject
- Aztecs.GL.Image.Internal: data ImageState
- Aztecs.GL.Image.Internal: instance GHC.Internal.Base.Monad m => Aztecs.ECS.Component.Component m Aztecs.GL.Image.Internal.ImageState
- Aztecs.GL.Internal: instance GHC.Internal.Base.Monad m => Aztecs.ECS.Component.Component m Aztecs.GL.Internal.OfMaterial
- Aztecs.GL.Internal: instance GHC.Internal.Base.Monad m => Aztecs.ECS.Component.Component m Aztecs.GL.Internal.OfMesh
- Aztecs.GL.Render: OfMaterial :: EntityID -> OfMaterial
- Aztecs.GL.Render: OfMesh :: EntityID -> OfMesh
- Aztecs.GL.Render: RenderGroups :: Map (EntityID, EntityID) [EntityID] -> RenderGroups
- Aztecs.GL.Render: [unOfMaterial] :: OfMaterial -> EntityID
- Aztecs.GL.Render: [unOfMesh] :: OfMesh -> EntityID
- Aztecs.GL.Render: [unRenderGroups] :: RenderGroups -> Map (EntityID, EntityID) [EntityID]
- Aztecs.GL.Render: newtype OfMaterial
- Aztecs.GL.Render: newtype OfMesh
- Aztecs.GL.Render: newtype RenderGroups
- Aztecs.GL.Render: renderGroups :: forall (m :: Type -> Type). Monad m => Access m (EntityID, RenderGroups)
- Aztecs.GL.Shape: Circle :: !Float -> !Int -> Circle
- Aztecs.GL.Shape: Rectangle :: !Float -> !Float -> Rectangle
- Aztecs.GL.Shape: Triangle :: !Float -> !Float -> !Float -> !Float -> !Float -> !Float -> Triangle
- Aztecs.GL.Shape: [circleRadius] :: Circle -> !Float
- Aztecs.GL.Shape: [circleSegments] :: Circle -> !Int
- Aztecs.GL.Shape: [rectangleHeight] :: Rectangle -> !Float
- Aztecs.GL.Shape: [rectangleWidth] :: Rectangle -> !Float
- Aztecs.GL.Shape: [triangleX1] :: Triangle -> !Float
- Aztecs.GL.Shape: [triangleX2] :: Triangle -> !Float
- Aztecs.GL.Shape: [triangleX3] :: Triangle -> !Float
- Aztecs.GL.Shape: [triangleY1] :: Triangle -> !Float
- Aztecs.GL.Shape: [triangleY2] :: Triangle -> !Float
- Aztecs.GL.Shape: [triangleY3] :: Triangle -> !Float
- Aztecs.GL.Shape: data Circle
- Aztecs.GL.Shape: data Rectangle
- Aztecs.GL.Shape: data Triangle
- Aztecs.GL.Shape: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.Shape.Circle
- Aztecs.GL.Shape: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.Shape.Rectangle
- Aztecs.GL.Shape: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.Shape.Triangle
- Aztecs.GL.Shape: instance GHC.Classes.Eq Aztecs.GL.Shape.Circle
- Aztecs.GL.Shape: instance GHC.Classes.Eq Aztecs.GL.Shape.Rectangle
- Aztecs.GL.Shape: instance GHC.Classes.Eq Aztecs.GL.Shape.Triangle
- Aztecs.GL.Shape: instance GHC.Internal.Show.Show Aztecs.GL.Shape.Circle
- Aztecs.GL.Shape: instance GHC.Internal.Show.Show Aztecs.GL.Shape.Rectangle
- Aztecs.GL.Shape: instance GHC.Internal.Show.Show Aztecs.GL.Shape.Triangle
- Aztecs.GL.Sprite: Sprite :: !EntityID -> Maybe (V4 Float) -> !V2 Float -> Sprite
- Aztecs.GL.Sprite: SpriteSheet :: !V2 Int -> !V2 Int -> !V2 Int -> !V2 Int -> SpriteSheet
- Aztecs.GL.Sprite: [spriteClip] :: Sprite -> Maybe (V4 Float)
- Aztecs.GL.Sprite: [spriteImage] :: Sprite -> !EntityID
- Aztecs.GL.Sprite: [spriteScale] :: Sprite -> !V2 Float
- Aztecs.GL.Sprite: [spriteSheetFrameGap] :: SpriteSheet -> !V2 Int
- Aztecs.GL.Sprite: [spriteSheetFrameIndex] :: SpriteSheet -> !V2 Int
- Aztecs.GL.Sprite: [spriteSheetFrameOffset] :: SpriteSheet -> !V2 Int
- Aztecs.GL.Sprite: [spriteSheetFrameSize] :: SpriteSheet -> !V2 Int
- Aztecs.GL.Sprite: data Sprite
- Aztecs.GL.Sprite: data SpriteSheet
- Aztecs.GL.Sprite: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.Sprite.Sprite
- Aztecs.GL.Sprite: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.Sprite.SpriteSheet
- Aztecs.GL.Sprite: instance GHC.Classes.Eq Aztecs.GL.Sprite.Sprite
- Aztecs.GL.Sprite: sprite :: EntityID -> Sprite
- Aztecs.GL.Sprite: spriteMaterial :: Sprite -> ImageState -> V2 Float -> Material
+ Aztecs.GL.D2: render :: forall (m :: Type -> Type). MonadIO m => Access m ()
+ Aztecs.GL.D2.Image: Image :: Image PixelRGBA8 -> !TextureFilter -> Image
+ Aztecs.GL.D2.Image: Linear' :: TextureFilter
+ Aztecs.GL.D2.Image: Nearest :: TextureFilter
+ Aztecs.GL.D2.Image: [imageData] :: Image -> Image PixelRGBA8
+ Aztecs.GL.D2.Image: [imageFilter] :: Image -> !TextureFilter
+ Aztecs.GL.D2.Image: data Image
+ Aztecs.GL.D2.Image: data TextureFilter
+ Aztecs.GL.D2.Image: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.D2.Image.Image
+ Aztecs.GL.D2.Image: loadImage :: FilePath -> IO Image
+ Aztecs.GL.D2.Image.Internal: ImageState :: !TextureObject -> !V2 Float -> ImageState
+ Aztecs.GL.D2.Image.Internal: [imageSize] :: ImageState -> !V2 Float
+ Aztecs.GL.D2.Image.Internal: [imageTexture] :: ImageState -> !TextureObject
+ Aztecs.GL.D2.Image.Internal: data ImageState
+ Aztecs.GL.D2.Image.Internal: instance GHC.Internal.Base.Monad m => Aztecs.ECS.Component.Component m Aztecs.GL.D2.Image.Internal.ImageState
+ Aztecs.GL.D2.Shape: Circle :: !Float -> !Int -> Circle
+ Aztecs.GL.D2.Shape: Rectangle :: !Float -> !Float -> Rectangle
+ Aztecs.GL.D2.Shape: Triangle :: !Float -> !Float -> !Float -> !Float -> !Float -> !Float -> Triangle
+ Aztecs.GL.D2.Shape: [circleRadius] :: Circle -> !Float
+ Aztecs.GL.D2.Shape: [circleSegments] :: Circle -> !Int
+ Aztecs.GL.D2.Shape: [rectangleHeight] :: Rectangle -> !Float
+ Aztecs.GL.D2.Shape: [rectangleWidth] :: Rectangle -> !Float
+ Aztecs.GL.D2.Shape: [triangleX1] :: Triangle -> !Float
+ Aztecs.GL.D2.Shape: [triangleX2] :: Triangle -> !Float
+ Aztecs.GL.D2.Shape: [triangleX3] :: Triangle -> !Float
+ Aztecs.GL.D2.Shape: [triangleY1] :: Triangle -> !Float
+ Aztecs.GL.D2.Shape: [triangleY2] :: Triangle -> !Float
+ Aztecs.GL.D2.Shape: [triangleY3] :: Triangle -> !Float
+ Aztecs.GL.D2.Shape: data Circle
+ Aztecs.GL.D2.Shape: data Rectangle
+ Aztecs.GL.D2.Shape: data Triangle
+ Aztecs.GL.D2.Shape: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.D2.Shape.Circle
+ Aztecs.GL.D2.Shape: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.D2.Shape.Rectangle
+ Aztecs.GL.D2.Shape: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.D2.Shape.Triangle
+ Aztecs.GL.D2.Shape: instance GHC.Classes.Eq Aztecs.GL.D2.Shape.Circle
+ Aztecs.GL.D2.Shape: instance GHC.Classes.Eq Aztecs.GL.D2.Shape.Rectangle
+ Aztecs.GL.D2.Shape: instance GHC.Classes.Eq Aztecs.GL.D2.Shape.Triangle
+ Aztecs.GL.D2.Shape: instance GHC.Internal.Show.Show Aztecs.GL.D2.Shape.Circle
+ Aztecs.GL.D2.Shape: instance GHC.Internal.Show.Show Aztecs.GL.D2.Shape.Rectangle
+ Aztecs.GL.D2.Shape: instance GHC.Internal.Show.Show Aztecs.GL.D2.Shape.Triangle
+ Aztecs.GL.D2.Sprite: Sprite :: !EntityID -> Maybe (V4 Float) -> !V2 Float -> Sprite
+ Aztecs.GL.D2.Sprite: SpriteSheet :: !V2 Int -> !V2 Int -> !V2 Int -> !V2 Int -> SpriteSheet
+ Aztecs.GL.D2.Sprite: [spriteClip] :: Sprite -> Maybe (V4 Float)
+ Aztecs.GL.D2.Sprite: [spriteImage] :: Sprite -> !EntityID
+ Aztecs.GL.D2.Sprite: [spriteScale] :: Sprite -> !V2 Float
+ Aztecs.GL.D2.Sprite: [spriteSheetFrameGap] :: SpriteSheet -> !V2 Int
+ Aztecs.GL.D2.Sprite: [spriteSheetFrameIndex] :: SpriteSheet -> !V2 Int
+ Aztecs.GL.D2.Sprite: [spriteSheetFrameOffset] :: SpriteSheet -> !V2 Int
+ Aztecs.GL.D2.Sprite: [spriteSheetFrameSize] :: SpriteSheet -> !V2 Int
+ Aztecs.GL.D2.Sprite: data Sprite
+ Aztecs.GL.D2.Sprite: data SpriteSheet
+ Aztecs.GL.D2.Sprite: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.D2.Sprite.Sprite
+ Aztecs.GL.D2.Sprite: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.D2.Sprite.SpriteSheet
+ Aztecs.GL.D2.Sprite: instance GHC.Classes.Eq Aztecs.GL.D2.Sprite.Sprite
+ Aztecs.GL.D2.Sprite: sprite :: EntityID -> Sprite
+ Aztecs.GL.D2.Sprite: spriteMaterial :: Sprite -> ImageState -> V2 Float -> Material
+ Aztecs.GL.D2.Sprite: spriteSheetClip :: SpriteSheet -> V4 Float
+ Aztecs.GL.Internal: RenderGroupKey :: EntityID -> EntityID -> RenderGroupKey
+ Aztecs.GL.Internal: [renderGroupKeyMaterial] :: RenderGroupKey -> EntityID
+ Aztecs.GL.Internal: [renderGroupKeyMesh] :: RenderGroupKey -> EntityID
+ Aztecs.GL.Internal: data RenderGroupKey
+ Aztecs.GL.Internal: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.Internal.OfMaterial
+ Aztecs.GL.Internal: instance Control.Monad.IO.Class.MonadIO m => Aztecs.ECS.Component.Component m Aztecs.GL.Internal.OfMesh
+ Aztecs.GL.Internal: instance GHC.Classes.Eq Aztecs.GL.Internal.RenderGroupKey
+ Aztecs.GL.Internal: instance GHC.Classes.Ord Aztecs.GL.Internal.RenderGroupKey
+ Aztecs.GL.Internal: instance GHC.Internal.Show.Show Aztecs.GL.Internal.RenderGroupKey
- Aztecs.GL.Internal: RenderGroups :: Map (EntityID, EntityID) [EntityID] -> RenderGroups
+ Aztecs.GL.Internal: RenderGroups :: Map RenderGroupKey (Set EntityID) -> RenderGroups
- Aztecs.GL.Internal: [unRenderGroups] :: RenderGroups -> Map (EntityID, EntityID) [EntityID]
+ Aztecs.GL.Internal: [unRenderGroups] :: RenderGroups -> Map RenderGroupKey (Set EntityID)
- Aztecs.GL.Internal: registerRenderable :: forall (m :: Type -> Type). Monad m => EntityID -> EntityID -> EntityID -> Access m ()
+ Aztecs.GL.Internal: registerRenderable :: forall (m :: Type -> Type). MonadIO m => EntityID -> EntityID -> EntityID -> Access m ()
- Aztecs.GL.Internal: unregisterRenderable :: forall (m :: Type -> Type). Monad m => EntityID -> EntityID -> EntityID -> Access m ()
+ Aztecs.GL.Internal: unregisterRenderable :: forall (m :: Type -> Type). MonadIO m => EntityID -> EntityID -> EntityID -> Access m ()

Files

CHANGELOG.md view
@@ -1,5 +1,3 @@ # Revision history for aztecs-gl -## 0.1.0.0 -- YYYY-mm-dd--* First version. Released on an unsuspecting world.+## [0.1.0](https://github.com/aztecs-hs/aztecs-gl/v0.1.0) - 2026-1-27
aztecs-gl.cabal view
@@ -1,6 +1,6 @@ cabal-version:   3.0 name:            aztecs-gl-version:         0.2.1+version:         0.3.0 license:         BSD-3-Clause license-file:    LICENSE maintainer:      matt@hunzinger.me@@ -19,14 +19,15 @@ library     exposed-modules:         Aztecs.GL-        Aztecs.GL.Image-        Aztecs.GL.Image.Internal+        Aztecs.GL.D2+        Aztecs.GL.D2.Image+        Aztecs.GL.D2.Image.Internal+        Aztecs.GL.D2.Shape+        Aztecs.GL.D2.Sprite         Aztecs.GL.Internal         Aztecs.GL.Material         Aztecs.GL.Mesh-        Aztecs.GL.Render-        Aztecs.GL.Shape-        Aztecs.GL.Sprite+              hs-source-dirs:   src     default-language: Haskell2010
src/Aztecs/GL.hs view
@@ -1,112 +1,13 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}- module Aztecs.GL   ( -- * OpenGL-    module Aztecs.GL.Image,     module Aztecs.GL.Material,     module Aztecs.GL.Mesh,-    module Aztecs.GL.Render,-    module Aztecs.GL.Shape,-    module Aztecs.GL.Sprite,-    render,      -- * Transform     module Aztecs.Transform,   ) where -import Aztecs-import Aztecs.GL.Image-import Aztecs.GL.Internal import Aztecs.GL.Material import Aztecs.GL.Mesh-import Aztecs.GL.Render-import Aztecs.GL.Shape-import Aztecs.GL.Sprite-import Aztecs.GLFW import Aztecs.Transform-import Control.Monad-import Control.Monad.IO.Class-import qualified Data.Map.Strict as Map-import Graphics.Rendering.OpenGL (($=))-import qualified Graphics.Rendering.OpenGL as GL-import qualified Graphics.UI.GLFW as GLFW-import Prelude hiding (lookup)---- | Render all entities with @Mesh@ or @OfMesh@, @Material@ or @OfMaterial@, and @Transform2D@ components-render :: (MonadIO m) => Access m ()-render = do-  windows <- system . readQuery $ (,) <$> query @_ @Window <*> query @_ @RawWindow-  forM_ windows $ \(window, RawWindow raw _) -> do-    liftIO $ do-      GLFW.makeContextCurrent (Just raw)--      -- Set viewport and clear-      GL.viewport $= (GL.Position 0 0, GL.Size (fromIntegral $ windowWidth window) (fromIntegral $ windowHeight window))-      GL.clearColor $= GL.Color4 0 0 0 1-      GL.clear [GL.ColorBuffer]--      -- Set up orthographic projection-      GL.matrixMode $= GL.Projection-      GL.loadIdentity-      GL.ortho 0 (fromIntegral $ windowWidth window) 0 (fromIntegral $ windowHeight window) (-1) 1-      GL.matrixMode $= GL.Modelview 0-      GL.loadIdentity--    -- Get render groups and render each group-    (_, RenderGroups groups) <- renderGroups-    forM_ (Map.toList groups) $ \((meshE, matE), entities) -> do-      mMeshState <- do-        mMeshState <- lookup meshE-        case mMeshState of-          Just ms -> return $ Just ms-          Nothing -> do-            mMesh <- lookup meshE-            case mMesh of-              Just mesh -> do-                ms <- liftIO $ unMesh mesh-                insert meshE $ bundle ms-                return $ Just ms-              Nothing -> return Nothing-      mMatState <- do-        mMatState <- lookup matE-        case mMatState of-          Just ms -> return $ Just ms-          Nothing -> do-            mMat <- lookup matE-            case mMat of-              Just mat -> do-                ms <- liftIO $ unMaterial mat-                insert matE $ bundle ms-                return $ Just ms-              Nothing -> return Nothing-      case (mMeshState, mMatState) of-        (Just meshState, Just matState) -> do-          transforms <- forM entities $ \e -> do-            mTrans <- lookup e-            return (e, mTrans)--          liftIO $ do-            materialPush matState-            forM_ transforms $ \(_, mTrans) ->-              case mTrans of-                Just trans -> renderMeshWithTransform meshState trans-                Nothing -> return ()-            materialPop matState-        _ -> return ()--renderMeshWithTransform :: MeshState -> Transform2D -> IO ()-renderMeshWithTransform md t = do-  let V2 tx ty = transformTranslation t-      V2 sx sy = transformScale t-      rot = transformRotation t-  GL.preservingMatrix $ do-    GL.translate $ GL.Vector3 (realToFrac tx) (realToFrac ty) (0 :: GL.GLfloat)-    GL.rotate (realToFrac rot) $ GL.Vector3 0 0 (1 :: GL.GLfloat)-    GL.scale (realToFrac sx) (realToFrac sy) (1 :: GL.GLfloat)-    meshPush md-    meshPop md
+ src/Aztecs/GL/D2.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Aztecs.GL.D2+  ( -- * OpenGL+    module Aztecs.GL.D2.Image,+    module Aztecs.GL.Material,+    module Aztecs.GL.Mesh,+    module Aztecs.GL.D2.Shape,+    module Aztecs.GL.D2.Sprite,+    render,++    -- * Transform+    module Aztecs.Transform,+  )+where++import Aztecs+import Aztecs.GL.D2.Image+import Aztecs.GL.D2.Shape+import Aztecs.GL.D2.Sprite+import Aztecs.GL.Internal+import Aztecs.GL.Material+import Aztecs.GL.Mesh+import Aztecs.GLFW+import Aztecs.Transform+import Control.Monad+import Control.Monad.IO.Class+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import Graphics.Rendering.OpenGL (($=))+import qualified Graphics.Rendering.OpenGL as GL+import qualified Graphics.UI.GLFW as GLFW+import Prelude hiding (lookup)++-- | Render all entities with @Mesh@ or @OfMesh@, @Material@ or @OfMaterial@, and @Transform2D@ components+render :: (MonadIO m) => Access m ()+render = do+  windows <- system . readQuery $ (,) <$> query @_ @Window <*> query @_ @RawWindow+  forM_ windows $ \(window, RawWindow raw _) -> do+    liftIO $ do+      GLFW.makeContextCurrent (Just raw)++      -- Set viewport and clear+      GL.viewport $= (GL.Position 0 0, GL.Size (fromIntegral $ windowWidth window) (fromIntegral $ windowHeight window))+      GL.clearColor $= GL.Color4 0 0 0 1+      GL.clear [GL.ColorBuffer]++      -- Set up orthographic projection+      GL.matrixMode $= GL.Projection+      GL.loadIdentity+      GL.ortho 0 (fromIntegral $ windowWidth window) 0 (fromIntegral $ windowHeight window) (-1) 1+      GL.matrixMode $= GL.Modelview 0+      GL.loadIdentity++    -- Get render groups and render each group+    (_, RenderGroups groups) <- renderGroups+    forM_ (Map.toList groups) $ \(RenderGroupKey meshE matE, entityEs) -> renderGroup meshE matE entityEs++-- | Render all entities with @Mesh@ or @OfMesh@, @Material@ or @OfMaterial@, and @Transform2D@ components+renderGroup :: (MonadIO m) => EntityID -> EntityID -> Set.Set EntityID -> Access m ()+renderGroup meshE matE entitySet = do+  let entities = Set.toList entitySet+  mMeshState <- do+    mMeshState <- lookup meshE+    case mMeshState of+      Just ms -> return $ Just ms+      Nothing -> do+        mMesh <- lookup meshE+        case mMesh of+          Just mesh -> do+            ms <- liftIO $ unMesh mesh+            insert meshE $ bundle ms+            return $ Just ms+          Nothing -> return Nothing+  mMatState <- do+    mMatState <- lookup matE+    case mMatState of+      Just ms -> return $ Just ms+      Nothing -> do+        mMat <- lookup matE+        case mMat of+          Just mat -> do+            ms <- liftIO $ unMaterial mat+            insert matE $ bundle ms+            return $ Just ms+          Nothing -> return Nothing+  case (mMeshState, mMatState) of+    (Just meshState, Just matState) -> do+      transforms <- forM entities $ \e -> do+        mTrans <- lookup e+        return (e, mTrans)++      liftIO $ do+        materialPush matState+        forM_ transforms $ \(_, mTrans) ->+          case mTrans of+            Just (GlobalTransform trans) -> renderMeshWithTransform meshState trans+            Nothing -> return ()+        materialPop matState+    _ -> return ()++renderMeshWithTransform :: MeshState -> Transform2D -> IO ()+renderMeshWithTransform md t = do+  let V2 tx ty = transformTranslation t+      V2 sx sy = transformScale t+      rot = transformRotation t+  GL.preservingMatrix $ do+    GL.translate $ GL.Vector3 (realToFrac tx) (realToFrac ty) (0 :: GL.GLfloat)+    GL.rotate (realToFrac rot) $ GL.Vector3 0 0 (1 :: GL.GLfloat)+    GL.scale (realToFrac sx) (realToFrac sy) (1 :: GL.GLfloat)+    meshPush md+    meshPop md
+ src/Aztecs/GL/D2/Image.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Aztecs.GL.D2.Image (Image (..), loadImage, TextureFilter (..)) where++import Aztecs+import Aztecs.GL.D2.Image.Internal+import Aztecs.GL.Internal+import Aztecs.Transform+import qualified Codec.Picture as JP+import Control.Monad+import Control.Monad.IO.Class+import qualified Data.ByteString as BS+import qualified Data.Vector.Storable as VS+import Foreign+import Graphics.Rendering.OpenGL (($=))+import qualified Graphics.Rendering.OpenGL as GL+import Graphics.Rendering.OpenGL.GL (TextureFilter (..))+import Prelude hiding (lookup)++-- | Image component+data Image = Image+  { -- | Image source data+    imageData :: JP.Image JP.PixelRGBA8,+    -- | Image texture filtering mode+    imageFilter :: !TextureFilter+  }++instance (MonadIO m) => Component m Image where+  componentOnInsert e img = inAnyWindowContext $ do+    let w = JP.imageWidth $ imageData img+        h = JP.imageHeight $ imageData img+        pixels = JP.imageData $ imageData img+        imgData = BS.pack $ VS.toList pixels++    res <- liftIO $ GL.genObjectNames 1+    tex <- case res of+      [tex] -> return tex+      _ -> error "Failed to generate texture object"++    liftIO $ do+      GL.textureBinding GL.Texture2D $= Just tex++      -- Set texture parameters (use Nearest filtering for crisp pixel art)+      GL.textureFilter GL.Texture2D $= ((imageFilter img, Nothing), imageFilter img)+      GL.textureWrapMode GL.Texture2D GL.S $= (GL.Repeated, GL.Repeat)+      GL.textureWrapMode GL.Texture2D GL.T $= (GL.Repeated, GL.Repeat)++      -- Upload texture data+      BS.useAsCString imgData $ \ptr -> do+        GL.texImage2D+          GL.Texture2D+          GL.NoProxy+          0+          GL.RGBA8+          (GL.TextureSize2D (fromIntegral w) (fromIntegral h))+          0+          (GL.PixelData GL.RGBA GL.UnsignedByte (castPtr ptr))+      GL.textureBinding GL.Texture2D $= Nothing+    insert e . bundle $ ImageState tex (V2 (fromIntegral w) (fromIntegral h))++loadImage :: FilePath -> IO Image+loadImage path = do+  eImg <- JP.readImage path+  case eImg of+    Left err -> error $ "Failed to load image: " ++ err+    Right dynImg -> let img = JP.convertRGBA8 dynImg in return $ Image img GL.Nearest
+ src/Aztecs/GL/D2/Image/Internal.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Aztecs.GL.D2.Image.Internal (ImageState (..)) where++import Aztecs+import Aztecs.Transform+import Control.Monad+import qualified Graphics.Rendering.OpenGL as GL+import Prelude hiding (lookup)++-- | Image state component+data ImageState = ImageState+  { imageTexture :: !GL.TextureObject,+    imageSize :: !(V2 Float)+  }++instance (Monad m) => Component m ImageState
+ src/Aztecs/GL/D2/Shape.hs view
@@ -0,0 +1,253 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Aztecs.GL.D2.Shape+  ( -- * Shapes+    Rectangle (..),+    Circle (..),+    Triangle (..),+  )+where++import Aztecs+import Aztecs.GL.Internal+import Aztecs.GL.Mesh+import Control.Monad+import Control.Monad.IO.Class+import qualified Data.Vector.Storable as SV+import Foreign.Ptr+import Foreign.Storable+import Graphics.Rendering.OpenGL (($=))+import qualified Graphics.Rendering.OpenGL as GL+import Prelude hiding (lookup)++-- | Rectangle component+data Rectangle = Rectangle+  { rectangleWidth :: !Float,+    rectangleHeight :: !Float+  }+  deriving (Show, Eq)++instance (MonadIO m) => Component m Rectangle where+  componentOnInsert e rect = inParentWindowContext e $ do+    let mesh = compileRectangle rect+    meshE <- spawn $ bundle mesh+    insert e $ bundle (OfMesh meshE)+  componentOnChange e oldRect newRect = when (oldRect /= newRect) . inParentWindowContext e $ do+    mMesh <- lookup e+    case mMesh of+      Just (OfMesh meshE) -> do+        mData <- lookup meshE+        case mData of+          Just md -> liftIO $ GL.deleteObjectName (meshVbo md)+          Nothing -> return ()+        let newMesh = compileRectangle newRect+        meshData <- liftIO $ unMesh newMesh+        insert meshE $ bundle meshData+      Nothing -> do+        let mesh = compileRectangle newRect+        meshE <- spawn $ bundle mesh+        insert e $ bundle (OfMesh meshE)+  componentOnRemove e _ = inParentWindowContext e $ do+    mMesh <- lookup e+    case mMesh of+      Just (OfMesh meshE) -> do+        mData <- lookup meshE+        case mData of+          Just md -> do+            liftIO $ GL.deleteObjectName (meshVbo md)+            despawn meshE+          Nothing -> return ()+        _ <- remove @_ @OfMesh e+        return ()+      Nothing -> return ()++-- | Circle component+data Circle = Circle+  { circleRadius :: !Float,+    circleSegments :: !Int+  }+  deriving (Show, Eq)++instance (MonadIO m) => Component m Circle where+  componentOnInsert e circ = inParentWindowContext e $ do+    let mesh = compileCircle circ+    meshE <- spawn $ bundle mesh+    insert e $ bundle (OfMesh meshE)+  componentOnChange e oldCirc newCirc = when (oldCirc /= newCirc) . inParentWindowContext e $ do+    mMesh <- lookup e+    case mMesh of+      Just (OfMesh meshE) -> do+        mData <- lookup meshE+        case mData of+          Just md -> liftIO $ GL.deleteObjectName (meshVbo md)+          Nothing -> return ()+        let newMesh = compileCircle newCirc+        meshData <- liftIO $ unMesh newMesh+        insert meshE $ bundle meshData+      Nothing -> do+        let mesh = compileCircle newCirc+        meshE <- spawn $ bundle mesh+        insert e $ bundle (OfMesh meshE)+  componentOnRemove e _ = inParentWindowContext e $ do+    mMesh <- lookup e+    case mMesh of+      Just (OfMesh meshE) -> do+        mData <- lookup meshE+        case mData of+          Just md -> do+            liftIO $ GL.deleteObjectName (meshVbo md)+            despawn meshE+          Nothing -> return ()+        _ <- remove @_ @OfMesh e+        return ()+      Nothing -> return ()++-- | Triangle component+data Triangle = Triangle+  { triangleX1 :: !Float,+    triangleY1 :: !Float,+    triangleX2 :: !Float,+    triangleY2 :: !Float,+    triangleX3 :: !Float,+    triangleY3 :: !Float+  }+  deriving (Show, Eq)++instance (MonadIO m) => Component m Triangle where+  componentOnInsert e tri = inParentWindowContext e $ do+    let mesh = compileTriangle tri+    meshE <- spawn $ bundle mesh+    insert e $ bundle (OfMesh meshE)+  componentOnChange e oldTri newTri = when (oldTri /= newTri) . inParentWindowContext e $ do+    mMesh <- lookup e+    case mMesh of+      Just (OfMesh meshE) -> do+        mData <- lookup meshE+        case mData of+          Just md -> liftIO $ GL.deleteObjectName (meshVbo md)+          Nothing -> return ()+        let newMesh = compileTriangle newTri+        meshData <- liftIO $ unMesh newMesh+        insert meshE $ bundle meshData+      Nothing -> do+        let mesh = compileTriangle newTri+        meshE <- spawn $ bundle mesh+        insert e $ bundle (OfMesh meshE)+  componentOnRemove e _ = inParentWindowContext e $ do+    mMesh <- lookup e+    case mMesh of+      Just (OfMesh meshE) -> do+        mData <- lookup meshE+        case mData of+          Just md -> do+            liftIO $ GL.deleteObjectName (meshVbo md)+            despawn meshE+          Nothing -> return ()+        _ <- remove @_ @OfMesh e+        return ()+      Nothing -> return ()++-- | Compile a rectangle into a VBO mesh with texture coordinates+compileRectangle :: Rectangle -> Mesh+compileRectangle (Rectangle w h) =+  let hw = w / 2+      hh = h / 2+      -- Interleaved: x, y, u, v for each vertex (V flipped for OpenGL)+      vertices =+        SV.fromList+          [ -hw,+            -hh,+            0,+            1, -- bottom-left+            hw,+            -hh,+            1,+            1, -- bottom-right+            hw,+            hh,+            1,+            0, -- top-right+            -hw,+            hh,+            0,+            0 -- top-left+          ] ::+          SV.Vector GL.GLfloat+   in compileMeshWithUV vertices GL.Quads++-- | Compile a circle into a VBO mesh+compileCircle :: Circle -> Mesh+compileCircle (Circle radius segments) =+  let angles = [2 * pi * fromIntegral i / fromIntegral segments | i <- [0 .. segments]]+      vertices = SV.fromList $ [0, 0] ++ concatMap (\a -> [radius * cos a, radius * sin a]) angles :: SV.Vector GL.GLfloat+   in compileMesh vertices GL.TriangleFan++-- | Compile a triangle into a VBO mesh+compileTriangle :: Triangle -> Mesh+compileTriangle (Triangle x1 y1 x2 y2 x3 y3) =+  let vertices = SV.fromList [x1, y1, x2, y2, x3, y3] :: SV.Vector GL.GLfloat+   in compileMesh vertices GL.Triangles++-- | Compile vertices into a VBO mesh (position only)+compileMesh :: SV.Vector GL.GLfloat -> GL.PrimitiveMode -> Mesh+compileMesh vertices mode = Mesh $ do+  let vertexCount = fromIntegral $ SV.length vertices `div` 2+  vbo <- uploadVbo vertices+  return $ simpleMeshState vbo vertexCount mode++-- | Compile vertices with UV into a VBO mesh (interleaved: x, y, u, v)+compileMeshWithUV :: SV.Vector GL.GLfloat -> GL.PrimitiveMode -> Mesh+compileMeshWithUV vertices mode = Mesh $ do+  let vertexCount = fromIntegral $ SV.length vertices `div` 4+  vbo <- uploadVbo vertices+  return $ texturedMeshState vbo vertexCount mode++-- | Upload vertex data to a new VBO+uploadVbo :: SV.Vector GL.GLfloat -> IO GL.BufferObject+uploadVbo vertices = do+  [vbo] <- GL.genObjectNames 1+  GL.bindBuffer GL.ArrayBuffer $= Just vbo+  SV.unsafeWith vertices $ \ptr -> do+    let dataSize = fromIntegral $ SV.length vertices * sizeOf (undefined :: GL.GLfloat)+    GL.bufferData GL.ArrayBuffer $= (dataSize, ptr, GL.StaticDraw)+  GL.bindBuffer GL.ArrayBuffer $= Nothing+  return vbo++-- | Create MeshState for non-textured mesh (position only, stride 0)+simpleMeshState :: GL.BufferObject -> GL.GLint -> GL.PrimitiveMode -> MeshState+simpleMeshState vbo vertexCount mode =+  MeshState+    { meshVbo = vbo,+      meshPush = do+        GL.bindBuffer GL.ArrayBuffer $= Just vbo+        GL.clientState GL.VertexArray $= GL.Enabled+        GL.arrayPointer GL.VertexArray $= GL.VertexArrayDescriptor 2 GL.Float 0 nullPtr+        GL.drawArrays mode 0 vertexCount,+      meshPop = do+        GL.clientState GL.VertexArray $= GL.Disabled+        GL.bindBuffer GL.ArrayBuffer $= Nothing+    }++-- | Create MeshState for textured mesh (interleaved x,y,u,v, stride 16)+texturedMeshState :: GL.BufferObject -> GL.GLint -> GL.PrimitiveMode -> MeshState+texturedMeshState vbo vertexCount mode =+  MeshState+    { meshVbo = vbo,+      meshPush = do+        GL.bindBuffer GL.ArrayBuffer $= Just vbo+        -- Position: 2 floats, stride 16 bytes (4 floats), offset 0+        GL.clientState GL.VertexArray $= GL.Enabled+        GL.arrayPointer GL.VertexArray $= GL.VertexArrayDescriptor 2 GL.Float 16 nullPtr+        -- UV: 2 floats, stride 16 bytes, offset 8 bytes+        GL.clientState GL.TextureCoordArray $= GL.Enabled+        GL.arrayPointer GL.TextureCoordArray $= GL.VertexArrayDescriptor 2 GL.Float 16 (plusPtr nullPtr 8)+        GL.drawArrays mode 0 vertexCount,+      meshPop = do+        GL.clientState GL.TextureCoordArray $= GL.Disabled+        GL.clientState GL.VertexArray $= GL.Disabled+        GL.bindBuffer GL.ArrayBuffer $= Nothing+    }
+ src/Aztecs/GL/D2/Sprite.hs view
@@ -0,0 +1,128 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Aztecs.GL.D2.Sprite+  ( -- * Sprites+    Sprite (..),+    sprite,+    spriteMaterial,++    -- * Sprite Sheets+    SpriteSheet (..),+    spriteSheetClip,+  )+where++import Aztecs+import Aztecs.GL.D2.Image.Internal+import Aztecs.GL.D2.Shape+import Aztecs.GL.Internal+import Aztecs.GL.Material+import Aztecs.Transform+import Control.Monad+import Control.Monad.IO.Class+import Graphics.Rendering.OpenGL (($=))+import qualified Graphics.Rendering.OpenGL as GL+import Prelude hiding (lookup)++-- | Sprite component+-- A sprite references an @Image@ component entity and renders it.+data Sprite = Sprite+  { -- | Image component entity ID+    spriteImage :: !EntityID,+    -- | Clip rectangle of the sprite in the texture (top, left, width, height).+    -- If 'Nothing', uses full image.+    spriteClip :: Maybe (V4 Float),+    -- | Scale to apply to UV coordinates+    spriteScale :: !(V2 Float)+  }+  deriving (Eq)++instance (MonadIO m) => Component m Sprite where+  componentOnInsert e s = do+    mImageState <- lookup $ spriteImage s+    case mImageState of+      Just imgState@(ImageState _ imgSize@(V2 w h)) ->+        let (rectW, rectH) = case spriteClip s of+              Just (V4 _ _ clipW clipH) -> (clipW, clipH)+              Nothing -> (w, h)+         in insert e $ bundle (spriteMaterial s imgState imgSize) <> bundle (Rectangle rectW rectH)+      Nothing -> return ()+  componentOnChange e old new = when (old /= new) $ componentOnInsert e new++-- | Sprite with an @EntityID@ to an @Image@ and the default configuration+sprite :: EntityID -> Sprite+sprite imgE =+  Sprite+    { spriteImage = imgE,+      spriteClip = Nothing,+      spriteScale = V2 1 1+    }++-- | Sprite material with UV offset and scale based on sprite fields+spriteMaterial :: Sprite -> ImageState -> V2 Float -> Material+spriteMaterial s (ImageState tex _) (V2 imgW imgH) =+  let V2 scaleU scaleV = spriteScale s+      -- Calculate UV offset and scale based on clip rectangle+      (uvOffsetU, uvOffsetV, uvScaleU, uvScaleV) = case spriteClip s of+        Just (V4 top left clipW clipH) ->+          ( left / imgW,+            top / imgH,+            (clipW / imgW) * scaleU,+            (clipH / imgH) * scaleV+          )+        Nothing ->+          (0, 0, scaleU, scaleV)+   in Material $+        pure+          MaterialState+            { materialPush = do+                GL.texture GL.Texture2D $= GL.Enabled+                GL.textureBinding GL.Texture2D $= Just tex+                GL.textureFunction $= GL.Replace+                GL.color $ GL.Color4 1 1 1 (1 :: GL.GLfloat)++                -- Apply UV transform via texture matrix+                GL.matrixMode $= GL.Texture+                GL.loadIdentity+                GL.translate $ GL.Vector3 (realToFrac uvOffsetU) (realToFrac uvOffsetV) (0 :: GL.GLfloat)+                GL.scale (realToFrac uvScaleU) (realToFrac uvScaleV) (1 :: GL.GLfloat)+                GL.matrixMode $= GL.Modelview 0,+              materialPop = do+                -- Reset texture matrix+                GL.matrixMode $= GL.Texture+                GL.loadIdentity+                GL.matrixMode $= GL.Modelview 0++                GL.texture GL.Texture2D $= GL.Disabled+                GL.textureBinding GL.Texture2D $= Nothing+            }++-- | Sprite sheet component that controls a @Sprite@ on the same entity+data SpriteSheet = SpriteSheet+  { spriteSheetFrameIndex :: !(V2 Int),+    spriteSheetFrameSize :: !(V2 Int),+    spriteSheetFrameOffset :: !(V2 Int),+    spriteSheetFrameGap :: !(V2 Int)+  }++instance (MonadIO m) => Component m SpriteSheet where+  componentOnInsert e sheet = do+    mSprite <- lookup e+    case mSprite of+      Just s ->+        let clip = spriteSheetClip sheet+         in insert e $ bundle s {spriteClip = Just clip}+      Nothing -> return ()+  componentOnChange e _ = componentOnInsert e++-- | Calculate the clip rectangle of a @SpriteSheet@+spriteSheetClip :: SpriteSheet -> V4 Float+spriteSheetClip sheet =+  let V2 frameX frameY = spriteSheetFrameIndex sheet+      V2 frameW frameH = spriteSheetFrameSize sheet+      V2 offsetX offsetY = spriteSheetFrameOffset sheet+      V2 gapX gapY = spriteSheetFrameGap sheet+      left = offsetX + frameX * (frameW + gapX)+      top = offsetY + frameY * (frameH + gapY)+   in V4 (fromIntegral top) (fromIntegral left) (fromIntegral frameW) (fromIntegral frameH)
− src/Aztecs/GL/Image.hs
@@ -1,66 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}--module Aztecs.GL.Image (Image (..), loadImage, TextureFilter (..)) where--import Aztecs-import Aztecs.GL.Image.Internal-import Aztecs.GL.Internal-import Aztecs.Transform-import qualified Codec.Picture as JP-import Control.Monad-import Control.Monad.IO.Class-import qualified Data.ByteString as BS-import qualified Data.Vector.Storable as VS-import Foreign-import Graphics.Rendering.OpenGL (($=))-import qualified Graphics.Rendering.OpenGL as GL-import Graphics.Rendering.OpenGL.GL (TextureFilter (..))-import Prelude hiding (lookup)---- | Image component-data Image = Image-  { -- | Image source data-    imageData :: JP.Image JP.PixelRGBA8,-    imageFilter :: !TextureFilter-  }--instance (MonadIO m) => Component m Image where-  componentOnInsert e img = inAnyWindowContext $ do-    let w = JP.imageWidth $ imageData img-        h = JP.imageHeight $ imageData img-        pixels = JP.imageData $ imageData img-        imgData = BS.pack $ VS.toList pixels--    res <- liftIO $ GL.genObjectNames 1-    tex <- case res of-      [tex] -> return tex-      _ -> error "Failed to generate texture object"--    liftIO $ do-      GL.textureBinding GL.Texture2D $= Just tex--      -- Set texture parameters (use Nearest filtering for crisp pixel art)-      GL.textureFilter GL.Texture2D $= ((imageFilter img, Nothing), imageFilter img)-      GL.textureWrapMode GL.Texture2D GL.S $= (GL.Repeated, GL.Repeat)-      GL.textureWrapMode GL.Texture2D GL.T $= (GL.Repeated, GL.Repeat)--      -- Upload texture data-      BS.useAsCString imgData $ \ptr -> do-        GL.texImage2D-          GL.Texture2D-          GL.NoProxy-          0-          GL.RGBA8-          (GL.TextureSize2D (fromIntegral w) (fromIntegral h))-          0-          (GL.PixelData GL.RGBA GL.UnsignedByte (castPtr ptr))-      GL.textureBinding GL.Texture2D $= Nothing-    insert e . bundle $ ImageState tex (V2 (fromIntegral w) (fromIntegral h))--loadImage :: FilePath -> IO Image-loadImage path = do-  eImg <- JP.readImage path-  case eImg of-    Left err -> error $ "Failed to load image: " ++ err-    Right dynImg -> let img = JP.convertRGBA8 dynImg in return $ Image img GL.Nearest
− src/Aztecs/GL/Image/Internal.hs
@@ -1,18 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}--module Aztecs.GL.Image.Internal (ImageState (..)) where--import Aztecs-import Aztecs.Transform-import Control.Monad-import qualified Graphics.Rendering.OpenGL as GL-import Prelude hiding (lookup)---- | Image state component-data ImageState = ImageState-  { imageTexture :: !GL.TextureObject,-    imageSize :: !(V2 Float)-  }--instance (Monad m) => Component m ImageState
src/Aztecs/GL/Internal.hs view
@@ -11,6 +11,7 @@     OfMaterial (..),     MeshState (..),     MaterialState (..),+    RenderGroupKey (..),     RenderGroups (..),     renderGroups,     registerRenderable,@@ -22,8 +23,12 @@ import Aztecs.GLFW import Control.Monad import Control.Monad.IO.Class+import Data.Map (Map) import qualified Data.Map.Strict as Map+import Data.Set (Set)+import qualified Data.Set as Set import qualified Data.Vector as V+import Debug.Trace import qualified Graphics.Rendering.OpenGL as GL import qualified Graphics.UI.GLFW as GLFW import Prelude hiding (lookup)@@ -51,7 +56,7 @@ newtype OfMesh = OfMesh {unOfMesh :: EntityID}   deriving (Show, Eq) -instance (Monad m) => Component m OfMesh where+instance (MonadIO m) => Component m OfMesh where   componentOnInsert e (OfMesh meshE) = do     mMat <- lookup e     case mMat of@@ -88,7 +93,7 @@ newtype OfMaterial = OfMaterial {unOfMaterial :: EntityID}   deriving (Show, Eq) -instance (Monad m) => Component m OfMaterial where+instance (MonadIO m) => Component m OfMaterial where   componentOnInsert e (OfMaterial matE) = do     mMesh <- lookup e     case mMesh of@@ -121,13 +126,22 @@           Just _ -> unregisterRenderable e e matE           Nothing -> return () +-- | Unique key for a render group+data RenderGroupKey = RenderGroupKey+  { -- | EntityID of the render group's mesh+    renderGroupKeyMesh :: EntityID,+    -- | EntityID of the render group's material+    renderGroupKeyMaterial :: EntityID+  }+  deriving (Show, Eq, Ord)+ -- | Render groups component-newtype RenderGroups = RenderGroups {unRenderGroups :: Map.Map (EntityID, EntityID) [EntityID]}+newtype RenderGroups = RenderGroups {unRenderGroups :: Map RenderGroupKey (Set EntityID)}   deriving (Show, Eq)  instance (Monad m) => Component m RenderGroups --- | Get or create the RenderGroups singleton+-- | Get or create the @RenderGroups@ singleton renderGroups :: (Monad m) => Access m (EntityID, RenderGroups) renderGroups = do   res <- system . readQuery $ (,) <$> entity <*> query @_ @RenderGroups@@ -139,23 +153,24 @@       return (e, rg)  -- | Register an entity as renderable with the given mesh and material entity IDs-registerRenderable :: (Monad m) => EntityID -> EntityID -> EntityID -> Access m ()+registerRenderable :: (MonadIO m) => EntityID -> EntityID -> EntityID -> Access m () registerRenderable e meshE matE = do+  liftIO $ traceIO $ "[RenderGroups] registerRenderable entity=" ++ show e ++ ", meshE=" ++ show meshE ++ ", matE=" ++ show matE   (rgE, RenderGroups groups) <- renderGroups-  let key = (meshE, matE)-      newGroups = Map.insertWith (++) key [e] groups+  let key = RenderGroupKey meshE matE+      newGroups = Map.insertWith Set.union key (Set.singleton e) groups   insert rgE $ bundle (RenderGroups newGroups)  -- | Unregister an entity from its render group-unregisterRenderable :: (Monad m) => EntityID -> EntityID -> EntityID -> Access m ()+unregisterRenderable :: (MonadIO m) => EntityID -> EntityID -> EntityID -> Access m () unregisterRenderable e meshE matE = do+  liftIO $ traceIO $ "[RenderGroups] unregisterRenderable entity=" ++ show e ++ ", meshE=" ++ show meshE ++ ", matE=" ++ show matE   (rgE, RenderGroups groups) <- renderGroups-  let key = (meshE, matE)+  let key = RenderGroupKey meshE matE       newGroups = Map.update removeEntity key groups       removeEntity es =-        case filter (/= e) es of-          [] -> Nothing-          es' -> Just es'+        let es' = Set.delete e es+         in if Set.null es' then Nothing else Just es'   insert rgE $ bundle (RenderGroups newGroups)  -- | Run an action in this entity's parent window's OpenGL context
src/Aztecs/GL/Material.hs view
@@ -19,7 +19,7 @@ import qualified Graphics.Rendering.OpenGL as GL import Prelude hiding (lookup) --- | Material component - wraps an IO action that produces MaterialState+-- | Material component newtype Material = Material {unMaterial :: IO MaterialState}  instance (MonadIO m) => Component m Material where@@ -34,11 +34,9 @@         case mMeshState of           Just _ -> registerRenderable e e e           Nothing -> return ()-   componentOnChange e _ new = do     matState <- liftIO $ unMaterial new     insert e $ bundle matState-   componentOnRemove e _ = do     mOfMesh <- lookup e     case mOfMesh of
− src/Aztecs/GL/Render.hs
@@ -1,15 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}--module Aztecs.GL.Render-  ( RenderGroups (..),-    renderGroups,-    OfMesh (..),-    OfMaterial (..),-  )-where--import Aztecs.GL.Internal (OfMaterial (..), OfMesh (..), RenderGroups (..), renderGroups)
− src/Aztecs/GL/Shape.hs
@@ -1,253 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}--module Aztecs.GL.Shape-  ( -- * Shapes-    Rectangle (..),-    Circle (..),-    Triangle (..),-  )-where--import Aztecs-import Aztecs.GL.Internal-import Aztecs.GL.Mesh-import Control.Monad-import Control.Monad.IO.Class-import qualified Data.Vector.Storable as SV-import Foreign.Ptr-import Foreign.Storable-import Graphics.Rendering.OpenGL (($=))-import qualified Graphics.Rendering.OpenGL as GL-import Prelude hiding (lookup)---- | Rectangle component-data Rectangle = Rectangle-  { rectangleWidth :: !Float,-    rectangleHeight :: !Float-  }-  deriving (Show, Eq)--instance (MonadIO m) => Component m Rectangle where-  componentOnInsert e rect = inParentWindowContext e $ do-    let mesh = compileRectangle rect-    meshE <- spawn $ bundle mesh-    insert e $ bundle (OfMesh meshE)-  componentOnChange e oldRect newRect = when (oldRect /= newRect) . inParentWindowContext e $ do-    mMesh <- lookup e-    case mMesh of-      Just (OfMesh meshE) -> do-        mData <- lookup meshE-        case mData of-          Just md -> liftIO $ GL.deleteObjectName (meshVbo md)-          Nothing -> return ()-        let newMesh = compileRectangle newRect-        meshData <- liftIO $ unMesh newMesh-        insert meshE $ bundle meshData-      Nothing -> do-        let mesh = compileRectangle newRect-        meshE <- spawn $ bundle mesh-        insert e $ bundle (OfMesh meshE)-  componentOnRemove e _ = inParentWindowContext e $ do-    mMesh <- lookup e-    case mMesh of-      Just (OfMesh meshE) -> do-        mData <- lookup meshE-        case mData of-          Just md -> do-            liftIO $ GL.deleteObjectName (meshVbo md)-            despawn meshE-          Nothing -> return ()-        _ <- remove @_ @OfMesh e-        return ()-      Nothing -> return ()---- | Circle component-data Circle = Circle-  { circleRadius :: !Float,-    circleSegments :: !Int-  }-  deriving (Show, Eq)--instance (MonadIO m) => Component m Circle where-  componentOnInsert e circ = inParentWindowContext e $ do-    let mesh = compileCircle circ-    meshE <- spawn $ bundle mesh-    insert e $ bundle (OfMesh meshE)-  componentOnChange e oldCirc newCirc = when (oldCirc /= newCirc) . inParentWindowContext e $ do-    mMesh <- lookup e-    case mMesh of-      Just (OfMesh meshE) -> do-        mData <- lookup meshE-        case mData of-          Just md -> liftIO $ GL.deleteObjectName (meshVbo md)-          Nothing -> return ()-        let newMesh = compileCircle newCirc-        meshData <- liftIO $ unMesh newMesh-        insert meshE $ bundle meshData-      Nothing -> do-        let mesh = compileCircle newCirc-        meshE <- spawn $ bundle mesh-        insert e $ bundle (OfMesh meshE)-  componentOnRemove e _ = inParentWindowContext e $ do-    mMesh <- lookup e-    case mMesh of-      Just (OfMesh meshE) -> do-        mData <- lookup meshE-        case mData of-          Just md -> do-            liftIO $ GL.deleteObjectName (meshVbo md)-            despawn meshE-          Nothing -> return ()-        _ <- remove @_ @OfMesh e-        return ()-      Nothing -> return ()---- | Triangle component-data Triangle = Triangle-  { triangleX1 :: !Float,-    triangleY1 :: !Float,-    triangleX2 :: !Float,-    triangleY2 :: !Float,-    triangleX3 :: !Float,-    triangleY3 :: !Float-  }-  deriving (Show, Eq)--instance (MonadIO m) => Component m Triangle where-  componentOnInsert e tri = inParentWindowContext e $ do-    let mesh = compileTriangle tri-    meshE <- spawn $ bundle mesh-    insert e $ bundle (OfMesh meshE)-  componentOnChange e oldTri newTri = when (oldTri /= newTri) . inParentWindowContext e $ do-    mMesh <- lookup e-    case mMesh of-      Just (OfMesh meshE) -> do-        mData <- lookup meshE-        case mData of-          Just md -> liftIO $ GL.deleteObjectName (meshVbo md)-          Nothing -> return ()-        let newMesh = compileTriangle newTri-        meshData <- liftIO $ unMesh newMesh-        insert meshE $ bundle meshData-      Nothing -> do-        let mesh = compileTriangle newTri-        meshE <- spawn $ bundle mesh-        insert e $ bundle (OfMesh meshE)-  componentOnRemove e _ = inParentWindowContext e $ do-    mMesh <- lookup e-    case mMesh of-      Just (OfMesh meshE) -> do-        mData <- lookup meshE-        case mData of-          Just md -> do-            liftIO $ GL.deleteObjectName (meshVbo md)-            despawn meshE-          Nothing -> return ()-        _ <- remove @_ @OfMesh e-        return ()-      Nothing -> return ()---- | Compile a rectangle into a VBO mesh with texture coordinates-compileRectangle :: Rectangle -> Mesh-compileRectangle (Rectangle w h) =-  let hw = w / 2-      hh = h / 2-      -- Interleaved: x, y, u, v for each vertex (V flipped for OpenGL)-      vertices =-        SV.fromList-          [ -hw,-            -hh,-            0,-            1, -- bottom-left-            hw,-            -hh,-            1,-            1, -- bottom-right-            hw,-            hh,-            1,-            0, -- top-right-            -hw,-            hh,-            0,-            0 -- top-left-          ] ::-          SV.Vector GL.GLfloat-   in compileMeshWithUV vertices GL.Quads---- | Compile a circle into a VBO mesh-compileCircle :: Circle -> Mesh-compileCircle (Circle radius segments) =-  let angles = [2 * pi * fromIntegral i / fromIntegral segments | i <- [0 .. segments]]-      vertices = SV.fromList $ [0, 0] ++ concatMap (\a -> [radius * cos a, radius * sin a]) angles :: SV.Vector GL.GLfloat-   in compileMesh vertices GL.TriangleFan---- | Compile a triangle into a VBO mesh-compileTriangle :: Triangle -> Mesh-compileTriangle (Triangle x1 y1 x2 y2 x3 y3) =-  let vertices = SV.fromList [x1, y1, x2, y2, x3, y3] :: SV.Vector GL.GLfloat-   in compileMesh vertices GL.Triangles---- | Compile vertices into a VBO mesh (position only)-compileMesh :: SV.Vector GL.GLfloat -> GL.PrimitiveMode -> Mesh-compileMesh vertices mode = Mesh $ do-  let vertexCount = fromIntegral $ SV.length vertices `div` 2-  vbo <- uploadVbo vertices-  return $ simpleMeshState vbo vertexCount mode---- | Compile vertices with UV into a VBO mesh (interleaved: x, y, u, v)-compileMeshWithUV :: SV.Vector GL.GLfloat -> GL.PrimitiveMode -> Mesh-compileMeshWithUV vertices mode = Mesh $ do-  let vertexCount = fromIntegral $ SV.length vertices `div` 4-  vbo <- uploadVbo vertices-  return $ texturedMeshState vbo vertexCount mode---- | Upload vertex data to a new VBO-uploadVbo :: SV.Vector GL.GLfloat -> IO GL.BufferObject-uploadVbo vertices = do-  [vbo] <- GL.genObjectNames 1-  GL.bindBuffer GL.ArrayBuffer $= Just vbo-  SV.unsafeWith vertices $ \ptr -> do-    let dataSize = fromIntegral $ SV.length vertices * sizeOf (undefined :: GL.GLfloat)-    GL.bufferData GL.ArrayBuffer $= (dataSize, ptr, GL.StaticDraw)-  GL.bindBuffer GL.ArrayBuffer $= Nothing-  return vbo---- | Create MeshState for non-textured mesh (position only, stride 0)-simpleMeshState :: GL.BufferObject -> GL.GLint -> GL.PrimitiveMode -> MeshState-simpleMeshState vbo vertexCount mode =-  MeshState-    { meshVbo = vbo,-      meshPush = do-        GL.bindBuffer GL.ArrayBuffer $= Just vbo-        GL.clientState GL.VertexArray $= GL.Enabled-        GL.arrayPointer GL.VertexArray $= GL.VertexArrayDescriptor 2 GL.Float 0 nullPtr-        GL.drawArrays mode 0 vertexCount,-      meshPop = do-        GL.clientState GL.VertexArray $= GL.Disabled-        GL.bindBuffer GL.ArrayBuffer $= Nothing-    }---- | Create MeshState for textured mesh (interleaved x,y,u,v, stride 16)-texturedMeshState :: GL.BufferObject -> GL.GLint -> GL.PrimitiveMode -> MeshState-texturedMeshState vbo vertexCount mode =-  MeshState-    { meshVbo = vbo,-      meshPush = do-        GL.bindBuffer GL.ArrayBuffer $= Just vbo-        -- Position: 2 floats, stride 16 bytes (4 floats), offset 0-        GL.clientState GL.VertexArray $= GL.Enabled-        GL.arrayPointer GL.VertexArray $= GL.VertexArrayDescriptor 2 GL.Float 16 nullPtr-        -- UV: 2 floats, stride 16 bytes, offset 8 bytes-        GL.clientState GL.TextureCoordArray $= GL.Enabled-        GL.arrayPointer GL.TextureCoordArray $= GL.VertexArrayDescriptor 2 GL.Float 16 (plusPtr nullPtr 8)-        GL.drawArrays mode 0 vertexCount,-      meshPop = do-        GL.clientState GL.TextureCoordArray $= GL.Disabled-        GL.clientState GL.VertexArray $= GL.Disabled-        GL.bindBuffer GL.ArrayBuffer $= Nothing-    }
− src/Aztecs/GL/Sprite.hs
@@ -1,135 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}--module Aztecs.GL.Sprite-  ( -- * Sprites-    Sprite (..),-    sprite,-    spriteMaterial,--    -- * Sprite Sheets-    SpriteSheet (..),-  )-where--import Aztecs-import Aztecs.GL.Image.Internal-import Aztecs.GL.Internal-import Aztecs.GL.Material-import Aztecs.GL.Shape-import Aztecs.Transform-import Control.Monad-import Control.Monad.IO.Class-import Graphics.Rendering.OpenGL (($=))-import qualified Graphics.Rendering.OpenGL as GL-import Prelude hiding (lookup)---- | Sprite component-data Sprite = Sprite-  { -- | Image component entity ID-    spriteImage :: !EntityID,-    -- | Clip rectangle of the sprite in the texture (top, left, width, height).-    -- If 'Nothing', uses full image.-    spriteClip :: Maybe (V4 Float),-    -- | Scale to apply to UV coordinates-    spriteScale :: !(V2 Float)-  }-  deriving (Eq)--instance (MonadIO m) => Component m Sprite where-  componentOnInsert e s = do-    mImageState <- lookup $ spriteImage s-    case mImageState of-      Just imgState@(ImageState _ imgSize@(V2 w h)) ->-        let (rectW, rectH) = case spriteClip s of-              Just (V4 _ _ clipW clipH) -> (clipW, clipH)-              Nothing -> (w, h)-         in insert e $ bundle (spriteMaterial s imgState imgSize) <> bundle (Rectangle rectW rectH)-      Nothing -> return ()-  componentOnChange e old new = when (old /= new) $ do-    mImageState <- lookup $ spriteImage new-    case mImageState of-      Just imgState@(ImageState _ imgSize@(V2 w h)) ->-        let (rectW, rectH) = case spriteClip new of-              Just (V4 _ _ clipW clipH) -> (clipW, clipH)-              Nothing -> (w, h)-         in insert e $ bundle (spriteMaterial new imgState imgSize) <> bundle (Rectangle rectW rectH)-      Nothing -> return ()--sprite :: EntityID -> Sprite-sprite imgE =-  Sprite-    { spriteImage = imgE,-      spriteClip = Nothing,-      spriteScale = V2 1 1-    }---- | Create a sprite material with UV offset and scale based on sprite fields-spriteMaterial :: Sprite -> ImageState -> V2 Float -> Material-spriteMaterial s (ImageState tex _) (V2 imgW imgH) =-  let V2 scaleU scaleV = spriteScale s-      -- Calculate UV offset and scale based on clip rectangle-      (uvOffsetU, uvOffsetV, uvScaleU, uvScaleV) = case spriteClip s of-        Just (V4 top left clipW clipH) ->-          ( left / imgW,-            top / imgH,-            (clipW / imgW) * scaleU,-            (clipH / imgH) * scaleV-          )-        Nothing ->-          (0, 0, scaleU, scaleV)-   in Material $-        pure-          MaterialState-            { materialPush = do-                GL.texture GL.Texture2D $= GL.Enabled-                GL.textureBinding GL.Texture2D $= Just tex-                GL.textureFunction $= GL.Replace-                GL.color $ GL.Color4 1 1 1 (1 :: GL.GLfloat)--                -- Apply UV transform via texture matrix-                GL.matrixMode $= GL.Texture-                GL.loadIdentity-                GL.translate $ GL.Vector3 (realToFrac uvOffsetU) (realToFrac uvOffsetV) (0 :: GL.GLfloat)-                GL.scale (realToFrac uvScaleU) (realToFrac uvScaleV) (1 :: GL.GLfloat)-                GL.matrixMode $= GL.Modelview 0,-              materialPop = do-                -- Reset texture matrix-                GL.matrixMode $= GL.Texture-                GL.loadIdentity-                GL.matrixMode $= GL.Modelview 0--                GL.texture GL.Texture2D $= GL.Disabled-                GL.textureBinding GL.Texture2D $= Nothing-            }---- | Sprite sheet component that controls a @Sprite@ on the same entity-data SpriteSheet = SpriteSheet-  { spriteSheetFrameIndex :: !(V2 Int),-    spriteSheetFrameSize :: !(V2 Int),-    spriteSheetFrameOffset :: !(V2 Int),-    spriteSheetFrameGap :: !(V2 Int)-  }--instance (MonadIO m) => Component m SpriteSheet where-  componentOnInsert = spriteSheetHook-  componentOnChange e _ = spriteSheetHook e--spriteSheetHook :: (MonadIO m) => EntityID -> SpriteSheet -> Access m ()-spriteSheetHook e sheet = do-  mSprite <- lookup e-  case mSprite of-    Just s ->-      let clip = spriteSheetClip sheet-       in insert e $ bundle s {spriteClip = Just clip}-    Nothing -> return ()--spriteSheetClip :: SpriteSheet -> V4 Float-spriteSheetClip sheet =-  let V2 frameX frameY = spriteSheetFrameIndex sheet-      V2 frameW frameH = spriteSheetFrameSize sheet-      V2 offsetX offsetY = spriteSheetFrameOffset sheet-      V2 gapX gapY = spriteSheetFrameGap sheet-      left = offsetX + frameX * (frameW + gapX)-      top = offsetY + frameY * (frameH + gapY)-   in V4 (fromIntegral top) (fromIntegral left) (fromIntegral frameW) (fromIntegral frameH)