-- $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))

	
	local x, y, z = Spring.GetUnitPosition(self.unit)
	if not x then
		return
	end
	local a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34, a41, a42, a43, a44 = Spring.GetUnitTransformMatrix(self.unit)
	
	gl.Translate(x - a41, y - a42, z - a43)
	
	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()
	if specFullView then
		return true
	end

	local losState = Spring.GetUnitLosState(self.unit, LocalAllyTeamID) or {}
	if (losState and losState.los) then
		return true
	end

	local x,y,z = Spring.GetUnitPosition(self.unit)
	if z and (GG or WG).Lups.IsPosInLos(x,y,z) and (Spring.IsSphereInView(x,y,z,(self.radius or 200)+100)) then
		return true
	end

	return false
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
