Carcigenicate
Carcigenicate

Reputation: 45736

error "Gloss / OpenGL Stack Overflow "after drawPicture."" when using a recursion based animation

Just to mess around with Haskell's Gloss library, I wrote:

import Graphics.Gloss

data World = World { worldBugs :: [Picture] }

bug :: Point -> Float -> Picture
bug (x, y) s =
    let head = Translate x (y - s) $ Circle (s * 0.8)
        body = Translate x (y + s) $ Circle (s * 1.2)
    in  pictures [head, body]

main = play (InWindow "Animation Test" (400, 400) (100, 100)) white 10
    (World . map (\(n,b) -> Translate (n * 20) (n * 20) $ b) $ zip [0..] $ replicate 100 $ bug (0,0) 100)
    (\world -> pictures $ worldBugs world)
    (\event world -> world)
    (\time (World bs) -> World $ map (Rotate (time * 10)) bs)

Which displays some "bugs" (two circles forming a head and torso) that rotate as time passes. The problem is, after a couple seconds of running, it crashes with:

Gloss / OpenGL Stack Overflow "after drawPicture."
  This program uses the Gloss vector graphics library, which tried to
  draw a picture using more nested transforms (Translate/Rotate/Scale)
  than your OpenGL implementation supports. The OpenGL spec requires
  all implementations to have a transform stack depth of at least 32,
  and Gloss tries not to push the stack when it doesn't have to, but
  that still wasn't enough.

  You should complain to your harware vendor that they don't provide
  a better way to handle this situation at the OpenGL API level.

  To make this program work you'll need to reduce the number of nested
  transforms used when defining the Picture given to Gloss. Sorry.

which, if I understand it correctly basically means that eventually, too many transformations are put onto a stack, and it overflows. It notes that this might be a hardware limitation (I'm on a Surface 2 Pro), so am I SOL? It doesn't do this when using animate, but that's probably due to the fact that it doesn't pass a state each tick.

If I'm going to make a game, I'm going have to use play to pass a state to the next tick; I can't base everything on time. Is there a way around this? Googling the error yields next to nothing.

Upvotes: 3

Views: 304

Answers (1)

Carcigenicate
Carcigenicate

Reputation: 45736

The problem is that on every "tick", it further nests the picture by wrapping it in another transformation (which eventually causes an overflow). To solve it, I just stored each of the values in a Entity object, then applied the transformations once at fromEntity.

    {- LANGUAGE threaded -}
module GlossTest where

import Graphics.Gloss

data Entity = Entity { entRot :: Float, entTrans :: Point, entScale :: Point, entPict :: Picture }

data World = World { worldBugs :: [Entity] }

entTranslate :: Float -> Float -> Entity -> Entity
entTranslate x y (Entity r t s p) = Entity r (x,y) s p

entRotate :: Float -> Entity -> Entity
entRotate x (Entity r t s p) = Entity x t s p

entRotateBy :: Float -> Entity -> Entity
entRotateBy n (Entity r t s p) = Entity (r + n) t s p

entMove :: Float -> Float -> Entity -> Entity
entMove x y (Entity r (tX,tY) s p) = Entity r (tX + x, tY + y) s p

toEntity :: Picture -> Entity
toEntity = Entity 0 (0,0) 1

fromEntity :: Entity -> Picture
fromEntity (Entity r (tX,tY) (sX,sY) p) = Rotate r . Translate tX tY $ Scale sX sY p

bug :: Point -> Float -> Entity
bug (x, y) s =
    let head = Rotate 0 $ Translate x (y - s) $ Circle (s * 0.8)
        body = Rotate 0 $ Translate x (y + s) $ Circle (s * 1.2)
    in  toEntity $ pictures [head, body]

main = play
    (InWindow "Animation Test" (400, 400) (100, 100)) white 1
    (World . map (\(n,b) -> entTranslate (n * 1) (n * 1) $ b) $ zip [0..] $ replicate 10 $ bug (0,0) 100)
    (\world -> pictures . map fromEntity $ worldBugs world)
    (\event world -> world)
    (\time (World bs) -> World $ map (\(n,b) -> entRotateBy (n * time) $ entMove time time b) $ zip [0..] bs)

Upvotes: 1

Related Questions