packages feed

aztecs-gl-0.3.0: src/Aztecs/GL/D2/Shape.hs

{-# 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
    }