-- $Id: StaticParticles.lua 3345 2008-12-02 00:03:50Z jk $
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

local StaticParticles = {}
StaticParticles.__index = StaticParticles

local billShader,sizeUniform,frameUniform
local colormapUniform,colorsUniform = {},0

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function StaticParticles.GetInfo()
  return {
    name      = "StaticParticles",
    backup    = "", --// backup class, if this class doesn't work (old cards,ati's,etc.)
    desc      = "",

    layer     = 0, --// extreme simply z-ordering :x

    --// gfx requirement
    fbo       = false,
    shader    = true,
    rtt       = false,
    ctt       = false,
    atiseries = 1,
  }
end

StaticParticles.Default = {
  particles = {},
  dlist     = 0,

  emitVector  = {0,1,0},
  pos         = {0,0,0}, --// start pos
  partpos     = "0,0,0", --// particle relative start pos (can contain lua code!)
  layer       = 0,

  life        = 0,
  lifeSpread  = 0,
  rot2Speed   = 0, --// global effect rotation
  size        = 0,
  sizeSpread  = 0,
  sizeGrowth  = 0,
  colormap    = { {0, 0, 0, 0} }, --//max 12 entries
  srcBlend    = GL.ONE,
  dstBlend    = GL.ONE_MINUS_SRC_ALPHA,
  alphaTest   = 0, --FIXME
  texture     = '',
  count       = 1,
  repeatEffect = false, --can be a number,too
}

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

--// speed ups

local abs  = math.abs
local sqrt = math.sqrt
local rand = math.random
local twopi= 2*math.pi
local cos  = math.cos
local sin  = math.sin
local min  = math.min

local spGetUnitViewPosition = Spring.GetUnitViewPosition
local spIsSphereInView      = Spring.IsSphereInView
local spGetUnitRadius       = Spring.GetUnitRadius

local glTexture     = gl.Texture 
local glBlending    = gl.Blending
local glUniform     = gl.Uniform
local glUniformInt  = gl.UniformInt
local glPushMatrix  = gl.PushMatrix
local glPopMatrix   = gl.PopMatrix
local glTranslate   = gl.Translate
local glCallList    = gl.CallList
local glRotate      = gl.Rotate
local glColor       = gl.Color

local glBeginEnd     = gl.BeginEnd
local GL_QUADS       = GL.QUADS
local glMultiTexCoord= gl.MultiTexCoord
local glVertex       = gl.Vertex

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function StaticParticles:CreateParticleAttributes(partpos,n)
  local life, pos, size;

  size   = rand()*self.sizeSpread
  life   = self.life + rand()*self.lifeSpread

  local part = {size=self.size+size,life=life,i=n}
  pos   = { ProcessParamCode(partpos, part) }

  return life, size, 
         pos[1],pos[2],pos[3];
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function StaticParticles:BeginDraw()
  gl.UseShader(billShader)
end

function StaticParticles:EndDraw()
  glTexture(false)
  glBlending(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA)
  gl.UseShader(0)
end

function StaticParticles:Draw()
  glTexture(self.texture)
  glBlending(self.srcBlend,self.dstBlend)

  glUniform(sizeUniform,self.usize)
  glUniform(frameUniform,self.frame)

  glUniformInt(colorsUniform, self.ncolors)
  for i=1,min(self.ncolors+1,12) do
    local color = self.colormap[i]
    glUniform( colormapUniform[i] , color[1], color[2], color[3], color[4] )
  end

  glPushMatrix()
  glTranslate(self.pos[1],self.pos[2],self.pos[3])
  glRotate(90,self.emitVector[1],self.emitVector[2],self.emitVector[3])
  glRotate(self.rot2Speed*self.frame,0,1,0)
    glCallList(self.dlist)
  glPopMatrix()
end


local function DrawParticleForDList(size,life,x,y,z,colors)
  glBeginEnd(GL_QUADS, function()
    glMultiTexCoord(0,x,y,z,1.0)
    glMultiTexCoord(1,size,colors/life)
    glVertex(-0.5,-0.5,0,0)
    glVertex( 0.5,-0.5,1,0)
    glVertex( 0.5, 0.5,1,1)
    glVertex(-0.5, 0.5,0,1)
  end)
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function StaticParticles:Initialize()
  billShader = gl.CreateShader({
    vertex = [[
      uniform float size;
      uniform float frame;

      uniform vec4 colormap[12];
      uniform int  colors;

varying vec2 texCoordFoo;

      void main()
      {
         //gl.MultiTexCoord(0,x,y,z,1.0)
         //gl.MultiTexCoord(1,size,colors/life)
         //gl.Vertex(ox,oy,s,t)

         float cpos      = frame*gl_MultiTexCoord1.y;
         int   ipos1     = int(cpos);
         float psize     = gl_MultiTexCoord1.x + size;

         if (ipos1>colors || psize<=0.0) {
           // paste dead particles offscreen, this way we don't dump the fragment shader with it
	vec4 offscreen = vec4(-2000.0,-2000.0,-2000.0,-2000.0);
           gl_Position = offscreen;
         }else{
           int ipos2 = ipos1+1; if (ipos2>colors) ipos2 = colors;
           gl_FrontColor = mix(colormap[ipos1],colormap[ipos2],fract(cpos));

           // calc vertex position
           vec4 pos        = gl_MultiTexCoord0;
           gl_Position     = gl_ModelViewMatrix * pos;

           // offset vertex from center of the polygon
           gl_Position.xy += gl_Vertex.xy * psize;

           // end
           gl_Position       = gl_ProjectionMatrix * gl_Position;
           texCoordFoo.st = gl_Vertex.pq;
         }
       }
    ]],
    fragment = [[
varying vec2 texCoordFoo;
      uniform sampler2D tex0;

      void main()
      {
        gl_FragColor = texture2D(tex0,texCoordFoo.st) * gl_Color;
      }
    ]],
    uniformInt = {
      tex0 = 0,
    },
    uniform = {
      frame = 0,
      size  = 0,
    },
  })

  if (billShader==nil) then
    print(PRIO_MAJOR,"LUPS->StaticParticles: Critical Shader Error: " ..gl.GetShaderLog())
    return false
  end

  sizeUniform   = gl.GetUniformLocation(billShader,"size")
  frameUniform  = gl.GetUniformLocation(billShader,"frame")

  colorsUniform = gl.GetUniformLocation(billShader,"colors")
  for i=1,12 do
    colormapUniform[i] = gl.GetUniformLocation(billShader,"colormap["..(i-1).."]")
  end
end

function StaticParticles:Finalize()
  gl.DeleteShader(billShader)
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function StaticParticles:Update(n)
  self.frame  = self.frame + n
  self.usize  = self.usize + n*self.sizeGrowth
end

-- used if repeatEffect=true;
function StaticParticles:ReInitialize()
  self.usize = self.size
  self.frame = 0
  self.dieGameFrame = self.dieGameFrame + self.life + self.lifeSpread
end

function StaticParticles:CreateParticle()
  local maxSpawnRadius = 0
  self.ncolors = #self.colormap-1
  local partposCode = ParseParamString(self.partpos)

  self.dlist = gl.CreateList( function()
    for i=1,self.count do
      local life,size,x,y,z = self:CreateParticleAttributes(partposCode,i-1)
      DrawParticleForDList(size,life,
                           x,y,z,    -- relative start pos
                           self.ncolors)
      local spawnDist = sqrt(x*x+y*y+z*z)
      if (spawnDist>maxSpawnRadius) then maxSpawnRadius=spawnDist end
    end
  end)

  self.usize = self.size
  self.frame = 0
  self.firstGameFrame = Spring.GetGameFrame()
  self.dieGameFrame   = self.firstGameFrame + self.life + self.lifeSpread

  --// visibility check vars
  self.radius        = self.size + self.sizeSpread + maxSpawnRadius + 100
  self.sphereGrowth  = self.sizeGrowth
end

function StaticParticles:Destroy()
  for _,part in ipairs(self.particles) do
    gl.DeleteList(part.dlist)
  end
  gl.DeleteTexture(self.texture)
end

function StaticParticles:Visible()
  local radius = self.radius +
                 self.frame*(self.sphereGrowth) --FIXME: frame is only updated on Update()
  local pos = {self.pos[1],self.pos[2],self.pos[3]}
  local losState
  if (self.unit and not self.worldspace) then
    local ux,uy,uz = spGetUnitViewPosition(self.unit)
    pos[1],pos[2],pos[3] = pos[1]+ux,pos[2]+uy,pos[3]+uz
    radius = radius + spGetUnitRadius(self.unit)
    losState = Spring.GetUnitLosState(self.unit, LocalAllyTeamID)
  end
  if (losState==nil) then
    local visible,los,radar = Spring.GetPositionLosState(pos[1],pos[2],pos[3], LocalAllyTeamID)
    losState = {los=los }
  end
  return (losState.los)and(spIsSphereInView(pos[1],pos[2],pos[3],radius))
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

function StaticParticles.Create(Options)
  local newObject = MergeTable(Options, StaticParticles.Default)
  setmetatable(newObject,StaticParticles)  --// make handle lookup
  newObject:CreateParticle()
  return newObject
end

-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

return StaticParticles