-- $Id: UnitCloaker.lua 3871 2009-01-28 00:09:23Z jk $
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

local UnitCloaker = {}
UnitCloaker.__index = UnitCloaker

local warpShader
local tex
local cameraUniform,lightUniform
local isS3oUniform, lifeUniform

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

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

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

    --// gfx requirement
    fbo       = true,
    shader    = true,
    rtt       = false,
    ctt       = true,
    intel     = 0,
  }
end

UnitCloaker.Default = {
  layer = 16,
  worldspace = true,

  inverse    = false,
  life       = math.huge,
  unit       = -1,
  unitDefID  = -1,

  repeatEffect = false,
  dieGameFrame = math.huge
}

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

local loadedS3oTexture = -1

function UnitCloaker:BeginDraw()
  gl.Culling(GL.FRONT)
  gl.Culling(true)
  gl.DepthMask(true)

  gl.UseShader(warpShader)
  local x,y,z = Spring.GetCameraPosition()
  gl.Uniform(cameraUniform,x,y,z)

  x,y,z = gl.GetSun( "pos" )
  gl.Light(0,GL.POSITION,x,y,z)

  gl.Uniform(lightUniform,x,y,z)

  x,y,z = gl.GetSun( "ambient" ,"unit")
  gl.Light(0,GL.AMBIENT,x,y,z)
  x,y,z = gl.GetSun( "diffuse" ,"unit")
  gl.Light(0,GL.DIFFUSE,x,y,z)

  --gl.Texture(1,'bitmaps/cdet.bmp')
  --gl.Texture(1,'bitmaps/clouddetail.bmp')
  --gl.Texture(1,'bitmaps/GPL/Lups/perlin_noise.jpg')
  gl.Texture(2,'bitmaps/GPL/Lups/mynoise2.png')
  gl.Texture(3,'$specular')
  gl.Texture(4,'$reflection')

  gl.MatrixMode(GL.PROJECTION)
  gl.PushMatrix()
  gl.MultMatrix("camera")
  gl.MatrixMode(GL.MODELVIEW)
  gl.PushMatrix()
  gl.LoadIdentity()
end

function UnitCloaker:EndDraw()
  gl.MatrixMode(GL.PROJECTION)
  gl.PopMatrix()
  gl.MatrixMode(GL.MODELVIEW)
  gl.PopMatrix()

  gl.Culling(GL.BACK)
  gl.Culling(false)
  gl.DepthMask(true)

  gl.UseShader(0)

  gl.Texture(0,false)
  gl.Texture(1,false)
  gl.Texture(2,false)
  gl.Texture(3,false)
  gl.Texture(4,false)

  gl.Color(1,1,1,1)

  loadedS3oTexture = -1
end

function UnitCloaker:Draw()
  local udid = 0
  if (self.isS3o) then
    udid = self.unitDefID
  end

  if (udid~=loadedS3oTexture) then
    gl.Texture(0, "%" .. udid .. ":0")
    gl.Texture(1, "%" .. udid .. ":1")
    loadedS3oTexture = udid
  end

  if (self.inverse) then
    gl.Uniform( lifeUniform, 1-(thisGameFrame-self.firstGameFrame)/self.life )
  else
    gl.Uniform( lifeUniform,   (thisGameFrame-self.firstGameFrame)/self.life )
  end

  gl.Color(Spring.GetTeamColor(self.team))

  if (self.isS3o) then
    gl.Culling(GL.BACK)
    gl.Unit(self.unit,true,-1)
    gl.Culling(GL.FRONT)
  else
    gl.Unit(self.unit,true,-1)
  end
end

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

function UnitCloaker.Initialize()
  warpShader = gl.CreateShader({
    vertex = [[
      uniform vec3 cameraPos;
      uniform vec3 lightPos;
      uniform float life;

      varying float opac;
      varying vec4  texCoord;
      varying vec3  normal;
      varying vec3  viewdir;

      //const vec4 ObjectPlaneS = vec4(0.002, 0.002, 0.0,   0.3);
      //const vec4 ObjectPlaneT = vec4(0.0,   0.002, 0.002, 0.3);

      const vec4 ObjectPlaneS = vec4(0.005, 0.005, 0.000,  0.0);
      const vec4 ObjectPlaneT = vec4(0.000, 0.005, 0.005,  0.0);

      void main(void)
      {
        texCoord.st  = gl_MultiTexCoord0.st;
        texCoord.p   = dot( gl_Vertex, ObjectPlaneS ); 
        texCoord.q   = dot( gl_Vertex, ObjectPlaneT );// + life*0.25;
        normal       = gl_NormalMatrix * gl_Normal;
        viewdir      = (gl_ModelViewMatrix * gl_Vertex).xyz - cameraPos;

        gl_FrontColor = gl_Color;

        float a = max( dot(normal, lightPos), 0.0);
        gl_FrontSecondaryColor.rgb = a * gl_LightSource[0].diffuse.rgb + gl_LightSource[0].ambient.rgb;

        opac = dot(normalize(normal), normalize(viewdir));
        opac = 1.0 - abs(opac);
        opac = pow(opac, 5.0);

        gl_Position = ftransform();
      }
    ]],
    fragment = [[
      uniform sampler2D texture1;
      uniform sampler2D texture2;
      uniform sampler2D noiseMap;
      uniform samplerCube specularMap;
      uniform samplerCube reflectMap;
      uniform float     life;

      varying float opac;
      varying vec4  texCoord;
      varying vec3  normal;
      varying vec3  viewdir;

      void main(void)
      {
          vec4 noise = texture2D(noiseMap, texCoord.pq);

          //if (noise.r < life) {
          //  discard;
          //}

          gl_FragColor     = texture2D(texture1, texCoord.st);
          gl_FragColor.rgb = mix(gl_FragColor.rgb, gl_Color.rgb, gl_FragColor.a);

          vec4 extraColor = texture2D(texture2, texCoord.st);

          vec3 reflectDir = reflect(viewdir, normalize(normal));

          vec3 spec = textureCube(specularMap, reflectDir).rgb * 4.0 * extraColor.g;
          vec3 refl = textureCube(reflectMap,  reflectDir).rgb;
          refl  = mix(gl_SecondaryColor.rgb, refl, extraColor.g);
          refl += extraColor.r;

          gl_FragColor.rgb = gl_FragColor.rgb * refl + spec;
          gl_FragColor.a   = extraColor.a;

          if (life*1.4>noise.r) {
            float d = life*1.4-noise.r;
            gl_FragColor.a *= smoothstep(0.4,0.0,d);
          }
          gl_FragColor.rgb += vec3(life*0.25);
      }
    ]],
    uniform = {
      isS3o    = false,
    },
    uniformInt = {
      texture1    = 0,
      texture2    = 1,
      noiseMap    = 2,
      specularMap = 3,
      reflectMap  = 4,
    },
    uniformFloat = {
      life  = 1,
    }
  })

  if (warpShader == nil) then
    print(PRIO_MAJOR,"LUPS->UnitCloaker: critical shader error: "..gl.GetShaderLog())
    return false
  end

  cameraUniform = gl.GetUniformLocation(warpShader, 'cameraPos')
  lightUniform  = gl.GetUniformLocation(warpShader, 'lightPos')
  lifeUniform   = gl.GetUniformLocation(warpShader, 'life')
end

function UnitCloaker.Finalize()
  if (gl.DeleteShader) then
    gl.DeleteShader(warpShader)
  end
end

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

-- used if repeatEffect=true;
function UnitCloaker:ReInitialize()
  self.dieGameFrame = self.dieGameFrame + self.life
end

function UnitCloaker:CreateParticle()
  local name = (UnitDefs[self.unitDefID].model and UnitDefs[self.unitDefID].model.name) or UnitDefs[self.unitDefID].modelname
  self.isS3o = ((name:lower():find("s3o") or name:lower():find("obj") or name:lower():find("dae")) and true)
  self.firstGameFrame = thisGameFrame
  self.dieGameFrame   = self.firstGameFrame + self.life
end

function UnitCloaker:Visible()
  if self.allyTeam == LocalAllyTeamID then
    return Spring.IsUnitVisible(self.unit, 0, true) -- Don't draw for icons
  end

  local _, specFullView = Spring.GetSpectatingState()
  local losState = Spring.GetUnitLosState(self.unit, LocalAllyTeamID) or {}
  return specFullView or (losState and losState.los)
end

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

function UnitCloaker.Create(Options)
  SetUnitLuaDraw(Options.unit,true)
  local newObject = MergeTable(Options, UnitCloaker.Default)
  setmetatable(newObject,UnitCloaker)  -- make handle lookup
  newObject:CreateParticle()
  return newObject
end

function UnitCloaker:Destroy()
  SetUnitLuaDraw(self.unit,false)
end

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

return UnitCloaker