Fireworks

Hi guys,

I’ve written a fireworks demo based on a nice JS tutorial (http://creativejs.com/tutorials/creating-fireworks/). I’m struggling a bit to improve the performance a bit. Any suggestions?

-- Main
displayMode(FULLSCREEN)

-- Use this function to perform your initial setup
function setup()
    fpsmin = 55555555
    fireworks = Fireworks()
    fireworks:addFirework()
    fireworks:addFirework()
    fireworks:addFirework()
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(0, 0, 0, 255)
    
    fill(231, 223, 231, 255)
    local fps = math.floor(1/DeltaTime)

    
    text("fps = "..fps, 100,100)
    fpsmin = math.min(fps, fpsmin)
    text("min fps = "..fpsmin, 100,150)
    
    fireworks:draw()
 
end 

function touched(touch)
    if touch.state == ENDED then
        fireworks:addFirework()
    end
end

Particle = class()

-- Represents a single point, so the firework being fired up
-- into the air, or a point in the exploded firework
function Particle:init(pos, target, vel, marker, usePhysics) 
  -- properties for animation
  -- and colouring
  self.alive = true
  self.GRAVITY  = -0.06
  self.alpha    = 1
  self.easing   = math.random() * 0.02
  self.fade     = math.random() * 0.15
  self.colors    = {
    vec3(9, 251, 237, 255),
    vec3(153, 255, 0, 255),
    vec3(208, 0, 255, 255),
    vec3(235, 14, 230, 255),
    vec3(0, 22, 255, 255),
    vec3(22, 246, 4, 255),
    vec3(9, 0, 255, 255),
    vec3(250, 255, 0, 255),
    vec3(255, 19, 0)
  }
  
  self.color_    = marker
 
  self.pos = vec2(
    pos.x or 0,
    pos.y or 0
  )
 
  self.vel = vec2(
    vel.x or 0,
    vel.y or 0
  )
 
  self.lastPos = vec2(
    self.pos.x,
    self.pos.y
  )
 
  self.target = vec2(0,
    target.y or 0
  )
 
  self.usePhysics = usePhysics or false
end

function Particle:draw()
    local x = math.ceil(self.pos.x)
    local y = math.ceil(self.pos.y)
    local xVel = (x - self.lastPos.x) * -3
    local yVel = (y - self.lastPos.y) * -3
    local firecolor = self.colors[self.color_]
    
    --tail
    smooth()
    strokeWidth(1)
    stroke(firecolor.x, firecolor.y, firecolor.z, 2550*self.alpha)
    line(self.pos.x-2.5, self.pos.y-2.5, self.pos.x + xVel, self.pos.y + yVel)
  
    -- glow
    smooth()
    tint(255, 255, 255, 150*self.alpha)
    sprite(fireworks.CIRCLEIMG, x - 3, y - 3, 15)
  
    -- colored spark
    noSmooth()
    tint(firecolor.x, firecolor.y, firecolor.z, 2550*self.alpha)
    sprite(fireworks.CIRCLEIMG, x - 3, y - 3)
end

function Particle:update()
    self.lastPos.x = self.pos.x
    self.lastPos.y = self.pos.y

    if self.usePhysics then
      self.vel.y = self.vel.y + self.GRAVITY
      self.pos.y = self.pos.y + self.vel.y

      -- since this value will drop below
      -- zero we'll occasionally see flicker,
      -- ... just like in real life! Woo! xD
      if self.alpha - self.fade >= 0 then
        self.alpha = self.alpha - self.fade
      else
        self.alpha = 0
      end
    else 

      local distance = (self.target.y - self.pos.y)

      -- ease the position
      self.pos.y = self.pos.y + distance * (0.03 + self.easing)

      -- cap to 1
      self.alpha = math.min(distance * distance * 0.00005, 1)
    end

    self.pos.x = self.pos.x + self.vel.x

    return self.alpha < 0.005
end

FireworksExplosion = class()

function FireworksExplosion:init()
    self.particles = {}
    self.ended = false
end

function FireworksExplosion:draw()
    local k = 1
    local particleDrawn = false
--    for i=1,table.getn(self.particles) do
  for i,firework in pairs(self.particles) do
        --local firework = self.particles[i]
        
        if firework and firework:update() then
            self.particles[i] = nil
            
            -- if not using physics, then explode
            if not firework.usePhysics then
                self:circle(firework)
            end        
        else
            self.particles[k] = self.particles[i]
            
            if k ~= i then
                self.particles[i] = nil
            end
            
            k = k + 1
        end
        
        if firework then
            particleDrawn = true
            firework:draw()
        end
    end
    
    if not particleDrawn then
        self.ended = true
    end
end

function FireworksExplosion:circle(firework)
    local count = 80
    local angle = (math.pi * 2) / count
    while count > 0 do
    
      local randomVelocity = 4 + math.random() * 4
      local particleAngle = count * angle

    self:createParticle(
        firework.pos,
        null,
        vec2 (
          math.cos(particleAngle) * randomVelocity,
          math.sin(particleAngle) * randomVelocity
        ),
        firework.color_,
        true) 
        count = count - 1
    end
end

 
function FireworksExplosion:createParticle(pos, target, vel, color_, usePhysics)
    pos = pos or {}
    target = target or {}
    vel = vel or {}

    table.insert(self.particles,
      Particle(
        -- position
        vec2(
          pos.x or WIDTH * 0.5,
          pos.y or 10
        ),

        -- target
        vec2(
          0,
          target.y or 2*HEIGHT/3 + math.random() * 100
        ),

        -- velocity
        vec2(
          vel.x or math.random() * 3 - 1.5,
          vel.y or 0
        ),

        color_ or math.floor(math.random() * 100) % 9 + 1,

        usePhysics)
    )
 
end

function FireworksExplosion:createFirework()
    self:createParticle()
end

Fireworks = class()

function Fireworks:init()
    self.explosions = {}
    fpsmin = 55555555
    self.CIRCLEIMG = self:circleImage(5)
end

function Fireworks:draw()
    for i, v in ipairs(self.explosions) do
        if not v.ended then
            v:draw()
        end
    end
end

function Fireworks:addFirework()
    local ex = FireworksExplosion()
    ex:createFirework()
    table.insert(self.explosions, ex)
end

function Fireworks:circleImage( size )
    local i = image(size,size)

    setContext(i)

    pushStyle()

    noStroke()
    fill(255) -- fill white, we can tint() the image

    ellipse( size/2, size/2, size )

    popStyle()

    setContext()

    return i
end

I’ve been digging around and found some performance tips on Lua, like using local (function) variables when they are heavily used in loops and such. I also have initialized the particles array so it has not memory allocation delay when particles are added.

But still the performance drops significally at the moment that the explosion takes place and a large amount (100) of particles needs to be created.

Anyone ideas what I missing here? Are class instantiations that expensive?

Cheers and thanks,

Doffer

Perhaps using meshes? I am not well versed in them as some here, but I believe they are the best way to do multiple sprites/draws when it comes to large numbers. Maybe someone here will be able to give some more input on this…

@Daemos thanks for your reply. I’ll give meshes a try, maybe that helps. On the other side I’m thinking of creating a pool of initialized particles. And reuse them instead of creating new ones.

Definitely use meshes for your particles, they make a huge difference, and they are pretty simple to use. You could still do pooling with meshes also…once you’ve added a rect to a mesh, you can’t remove without reloading all of the verts in the mesh, so it makes sense to reuse them.

classes are not an integral part of lua, so creating them is - relatively - expensive. For something different, maybe you would like to try to rewrite the fireworks stuff using closures for the particles. Creating closures is a lot faster, plus everything related to the particle could be local to the closure and thus a lot faster than the table accesses required for the solution using classes.

Thanks for your replies. I’ll give it a try.

A quick update. The mesh rendering did the trick. I can now render with acceptable frame rates.
Thanks for the help all.