-- $Id: NanoLasers.lua 3357 2008-12-05 11:08:54Z jk $
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------

local NanoLasers = {}
NanoLasers.__index = NanoLasers

local dlist
local laserShader

local knownNanoLasers = {}

local lastTexture = ""

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

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

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

NanoLasers.Default = {
	layer        = 0,
	worldspace   = true,
	repeatEffect = false,

	--// shared options with all nanofx
	pos          = {0,0,0}, --// start pos
	targetpos    = {0,0,0},
	targetradius = 0,       --// terraform/unit radius
	color        = {0, 0, 0, 0},
	count        = 1,
	inversed     = false,   --// reclaim?
	terraform    = false,   --// for terraform (2d target)
	unit         = -1,
	nanopiece    = -1,

	--// some unit informations
	targetID  = -1,
	unitID    = -1,
	unitpiece = -1,
	unitDefID = -1,
	teamID    = -1,
	allyID    = -1,

	--// custom (user) options
	life            = 30,
	flare           = false,
	streamSpeed     = 10,
	streamThickness = -1,  --//streamThickness =  4+self.count*0.34,
	corethickness   = 1,
	corealpha       = 1,
	texture         = "bitmaps/largelaserfalloff.tga",
}

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

--//speed ups

local GL_ONE       = GL.ONE
local GL_SRC_ALPHA = GL.SRC_ALPHA
local GL_ONE_MINUS_SRC_ALPHA = GL.ONE_MINUS_SRC_ALPHA
local GL_QUADS     = GL.QUADS
local GL_TEXTURE   = GL.TEXTURE
local GL_MODELVIEW = GL.MODELVIEW

local glColor      = gl.Color
local glTexture    = gl.Texture
local glBlending   = gl.Blending
local glMultiTexCoord   = gl.MultiTexCoord
local glVertex     = gl.Vertex
local glTranslate  = gl.Translate
local glMatrixMode = gl.MatrixMode
local glPushMatrix = gl.PushMatrix
local glPopMatrix  = gl.PopMatrix
local glBeginEnd   = gl.BeginEnd
local glUseShader  = gl.UseShader
local glAlphaTest  = gl.AlphaTest
local glCallList   = gl.CallList

local max  = math.max

local GetCameraVectors    = Spring.GetCameraVectors
local IsSphereInView      = Spring.IsSphereInView

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

function NanoLasers:BeginDraw()
	glUseShader(laserShader)
	glBlending(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
	glAlphaTest(false)
end


function NanoLasers:EndDraw()
	glUseShader(0)
	glColor(1,1,1,1)
	glTexture(false)
	glBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
	glAlphaTest(true)

	-- i hate that nv bug!
	glMultiTexCoord(0,0,0,0)
	glMultiTexCoord(1,0,0,0)
	glMultiTexCoord(2,0,0,0)
	glMultiTexCoord(3,0,0,0)

	lastTexture=""
end


function NanoLasers:Draw()
	if (lastTexture~=self.texture) then
		glTexture(self.texture)
		lastTexture=self.texture
	end

	local color = self.color
	local startPos = self.pos
	local endPos   = self.targetpos
	
	glColor(color[1],color[2],color[3],0.0003)
	glMultiTexCoord(0,endPos[1] - self.normdir[3] * self.scane_mult ,endPos[2],endPos[3] + self.normdir[1] * self.scane_mult,1)
	glMultiTexCoord(1,startPos[1],startPos[2],startPos[3],1)

	if (self.inversed) then
		glMultiTexCoord(2,  (thisGameFrame+Spring.GetFrameTimeOffset())*self.streamSpeed, self.streamThickness, self.corealpha, self.corethickness)
	else
		glMultiTexCoord(2, -(thisGameFrame+Spring.GetFrameTimeOffset())*self.streamSpeed, self.streamThickness, self.corealpha, self.corethickness)
	end

	glCallList(dlist)
end

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

function NanoLasers:Update(n)
	UpdateNanoParticles(self)
	
	self.fpos = (self.fpos or 0) + self.count * 5 * n
	if (self.inversed) then
		self.scane_mult = 4 * math.cos(6*(self.fpos%4001)/4000*math.pi)
	else
		self.scane_mult = 8 * math.cos(2*(self.fpos%4001)/4000*math.pi)
	end

	if (self._dead) then
		RemoveParticles(self.id)
	end
end

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

function NanoLasers:Visible()
	if (self.allyID ~= LocalAllyTeamID) and (LocalAllyTeamID >= 0) and(self.visibility == 0) then
		return false
	end

	local midPos = self._midpos
	return IsSphereInView(midPos[1],midPos[2],midPos[3], self._radius)
end

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

function NanoLasers:Initialize()
	laserShader = gl.CreateShader({
		vertex = [[
			//gl.vertex.xy := length,width
			//gl.vertex.zw := texcoord

			#define startpos         gl_MultiTexCoord0
			#define endpos           gl_MultiTexCoord1
			#define streamTranslate  gl_MultiTexCoord2.x
			#define streamThickness  gl_MultiTexCoord2.y
			#define coreAlpha        gl_MultiTexCoord2.z
			#define coreThickness    gl_MultiTexCoord2.w
			#define isCore           gl_MultiTexCoord3.x

			varying vec3 texCoord;

			void main()
			{
				texCoord    =  vec3(gl_Vertex.z - streamTranslate, gl_Vertex.w, gl_Vertex.z);
				vec3 dir3;
				if (gl_Vertex.x>0.5) {
					gl_Position = (gl_ModelViewMatrix * endpos);
					dir3   = gl_Position.xyz - (gl_ModelViewMatrix * (startpos)).xyz;
				} else {
					gl_Position = (gl_ModelViewMatrix * startpos);
					dir3   = (gl_ModelViewMatrix * (endpos)).xyz - gl_Position.xyz;
				}
				vec3 v = normalize( dir3 );
				vec3 w = normalize( -gl_Position.xyz );
				vec3 u = normalize( cross(w,v) );

				if (isCore>0.0) {
					gl_Position.xyz  += (gl_Vertex.y * coreThickness) * u;
					gl_FrontColor.rgb = vec3(coreAlpha);
					gl_FrontColor.a   = 0.003;
				}else{
					gl_Position.xyz += (gl_Vertex.y * streamThickness) * u;
					gl_FrontColor    = gl_Color;
				}

				gl_Position      = gl_ProjectionMatrix * gl_Position;
			}
		]],
		fragment = [[
			uniform sampler2D tex0;

			varying vec3 texCoord;

			void main()
			{
				 gl_FragColor = texture2D(tex0, texCoord.st) * gl_Color;
				 if (texCoord.p>0.95) gl_FragColor *= vec4(1.0 - texCoord.p) * 20.0;
				 if (texCoord.p<0.05) gl_FragColor *= vec4(texCoord.p) * 20.0;
			}
		]],
		uniformInt = {
			tex0=0,
		}
	})


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

	dlist = gl.CreateList(glBeginEnd,GL_QUADS,function()
		glMultiTexCoord(3,0)
		glVertex(1,-1, 1,0)
		glVertex(0,-1, 0,0)
		glVertex(0, 1, 0,1)
		glVertex(1, 1, 1,1)

		glMultiTexCoord(3,1)
		glVertex(1,-1, 1,0)
		glVertex(0,-1, 0,0)
		glVertex(0, 1, 0,1)
		glVertex(1, 1, 1,1)
	end)
end

function NanoLasers:Finalize()
	if (gl.DeleteShader) then
		gl.DeleteShader(laserShader)
	end
	gl.DeleteList(dlist)
end

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

function NanoLasers:CreateParticle()
	self.life            = self.life + 1 --// so we can reuse existing fx's
	self.firstGameFrame  = thisGameFrame
	self.dieGameFrame    = self.firstGameFrame + self.life
	self._staticTarget   = false
	self._lastupdate_los = nil
	local GLOBAL = (WG or GG)

	if (self.flare) then
		--[[if you add those flares, then the laser is slower as the engine, so it needs some tweaking]]--
		if (self.flare1id and GLOBAL.Lups.GetParticles(self.flare1id) and GLOBAL.Lups.GetParticles(self.flare2id)) then
			local flare1 = GLOBAL.Lups.GetParticles(self.flare1id)
			flare1.size  = self.count*0.1
			flare1:ReInitialize()
			local flare2 = GLOBAL.Lups.GetParticles(self.flare2id)
			flare2.size  = self.count*0.75
			flare2:ReInitialize()
			return
		else
			local r,g,b = max(self.color[1],0.13),max(self.color[2],0.13),max(self.color[3],0.13)
			local flare = {
				unit         = self.unitID,
				piecenum     = self.unitpiece,
				layer        = self.layer,
				life         = 31,
				size         = self.count*0.1,
				sizeSpread   = 1,
				sizeGrowth   = 0.1,
				colormap     = { {r*2,g*2,b*2,0.01},{r*2,g*2,b*2,0.01},{r*2,g*2,b*2,0.01} },
				texture      = 'bitmaps/GPL/groundflash.tga',
				count        = 2,
				repeatEffect = false,
			}
			self.flare1id  = AddParticles("StaticParticles",flare)
			flare.size     = self.count*0.75
			flare.texture  = 'bitmaps/flare.tga'
			flare.colormap = { {r*2,g*2,b*2,0.009},{r*2,g*2,b*2,0.009},{r*2,g*2,b*2,0.009} }
			flare.count    = 2
			self.flare2id  = AddParticles("StaticParticles",flare)
		end
	end


	self.visibility = 0
	self:Update(0) --//update los

	if (self.streamThickness<0) then
		self.streamThickness = 4+self.count*0.34
	end
end

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

function NanoLasers.Create(Options)
	local unit,nanopiece=Options.unitID,Options.nanopiece
	if (unit and nanopiece)and(knownNanoLasers[unit])and(knownNanoLasers[unit][nanopiece]) then
		local reuseFx = knownNanoLasers[unit][nanopiece]
		CopyTable(reuseFx,Options)
		reuseFx:CreateParticle()
		return false,reuseFx.id
	else
		local newObject = MergeTable(Options, NanoLasers.Default)
		setmetatable(newObject,NanoLasers)  -- make handle lookup
		newObject:CreateParticle()

		if (unit and nanopiece) then
			if (not knownNanoLasers[unit]) then
				knownNanoLasers[unit] = {}
			end
			knownNanoLasers[unit][nanopiece] = newObject
		end

		return newObject
	end
end

function NanoLasers:Destroy()
	local unit,nanopiece=self.unitID,self.nanopiece
	knownNanoLasers[unit][nanopiece] = nil
	if (not next(knownNanoLasers[unit])) then
		knownNanoLasers[unit] = nil
	end

	if (self.flare) then
		RemoveParticles(self.flare1id)
		RemoveParticles(self.flare2id)
	end
end

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

return NanoLasers
