
layout(location = 0) in vec4 aPosition;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec4 aColor;

layout(location = 0) out vec4 vTexCoord;
layout(location = 1) out vec4 vColor;
layout(location = 9) out vec3 vLightmap;

#ifndef SIMPLE	// we do not need these for simple shaders
layout(location = 3) in vec4 aVertex2;
layout(location = 4) in vec4 aNormal;
layout(location = 5) in vec4 aNormal2;
layout(location = 6) in vec3 aLightmap;
layout(location = 7) in vec4 aBoneWeight;
layout(location = 8) in uvec4 aBoneSelector;

layout(location = 2) out vec4 pixelpos;
layout(location = 3) out vec3 glowdist;
layout(location = 4) out vec3 gradientdist;
layout(location = 5) out vec4 vWorldNormal;
layout(location = 6) out vec4 vEyeNormal;
#endif

#ifdef NO_CLIPDISTANCE_SUPPORT
layout(location = 7) out vec4 ClipDistanceA;
layout(location = 8) out vec4 ClipDistanceB;
#endif

struct BonesResult
{
	vec3 Normal;
	vec4 Position;
};

BonesResult ApplyBones();

void main()
{
	float ClipDistance0, ClipDistance1, ClipDistance2, ClipDistance3, ClipDistance4;

	vec2 parmTexCoord;
	vec4 parmPosition;

	BonesResult bones = ApplyBones();

	parmTexCoord = aTexCoord;
	parmPosition = bones.Position;
	
	#ifndef SIMPLE
		vec4 worldcoord = ModelMatrix * mix(parmPosition, aVertex2, uInterpolationFactor);
	#else
		vec4 worldcoord = ModelMatrix * parmPosition;
	#endif

	vec4 eyeCoordPos = ViewMatrix * worldcoord;

	#ifdef HAS_UNIFORM_VERTEX_DATA
		if ((useVertexData & 1) == 0)
			vColor = uVertexColor;
		else
			vColor = aColor;
	#else
		vColor = aColor;
	#endif

	#ifndef SIMPLE
		vLightmap = aLightmap;

		pixelpos.xyz = worldcoord.xyz;
		pixelpos.w = -eyeCoordPos.z/eyeCoordPos.w;

		if (uGlowTopColor.a > 0 || uGlowBottomColor.a > 0)
		{
			float topatpoint = (uGlowTopPlane.w + uGlowTopPlane.x * worldcoord.x + uGlowTopPlane.y * worldcoord.z) * uGlowTopPlane.z;
			float bottomatpoint = (uGlowBottomPlane.w + uGlowBottomPlane.x * worldcoord.x + uGlowBottomPlane.y * worldcoord.z) * uGlowBottomPlane.z;
			glowdist.x = topatpoint - worldcoord.y;
			glowdist.y = worldcoord.y - bottomatpoint;
			glowdist.z = clamp(glowdist.x / (topatpoint - bottomatpoint), 0.0, 1.0);
		}
		
		if (uObjectColor2.a != 0)
		{
			float topatpoint = (uGradientTopPlane.w + uGradientTopPlane.x * worldcoord.x + uGradientTopPlane.y * worldcoord.z) * uGradientTopPlane.z;
			float bottomatpoint = (uGradientBottomPlane.w + uGradientBottomPlane.x * worldcoord.x + uGradientBottomPlane.y * worldcoord.z) * uGradientBottomPlane.z;
			gradientdist.x = topatpoint - worldcoord.y;
			gradientdist.y = worldcoord.y - bottomatpoint;
			gradientdist.z = clamp(gradientdist.x / (topatpoint - bottomatpoint), 0.0, 1.0);
		}
		
		if (uSplitBottomPlane.z != 0.0)
		{
			ClipDistance3 = ((uSplitTopPlane.w + uSplitTopPlane.x * worldcoord.x + uSplitTopPlane.y * worldcoord.z) * uSplitTopPlane.z) - worldcoord.y;
			ClipDistance4 = worldcoord.y - ((uSplitBottomPlane.w + uSplitBottomPlane.x * worldcoord.x + uSplitBottomPlane.y * worldcoord.z) * uSplitBottomPlane.z);
		}

		vWorldNormal = NormalModelMatrix * vec4(normalize(bones.Normal), 1.0);
		vEyeNormal = NormalViewMatrix * vec4(normalize(vWorldNormal.xyz), 1.0);
	#endif
	
	#ifdef SPHEREMAP
		vec3 u = normalize(eyeCoordPos.xyz);
		vec4 n = normalize(NormalViewMatrix * vec4(parmTexCoord.x, 0.0, parmTexCoord.y, 0.0));
		vec3 r = reflect(u, n.xyz);
		float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) );
		vec2 sst = vec2(r.x/m + 0.5,  r.y/m + 0.5);
		vTexCoord.xy = sst;
	#else
		vTexCoord = TextureMatrix * vec4(parmTexCoord, 0.0, 1.0);
	#endif
	
	gl_Position = ProjectionMatrix * eyeCoordPos;

	#ifdef VULKAN_COORDINATE_SYSTEM
	gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0;
	#endif

	if (uClipHeightDirection != 0.0) // clip planes used for reflective flats
	{
		ClipDistance0 = (worldcoord.y - uClipHeight) * uClipHeightDirection;
	}
	else if (uClipLine.x > -1000000.0) // and for line portals - this will never be active at the same time as the reflective planes clipping so it can use the same hardware clip plane.
	{
		ClipDistance0 = -( (worldcoord.z - uClipLine.y) * uClipLine.z + (uClipLine.x - worldcoord.x) * uClipLine.w ) + 1.0/32768.0;	// allow a tiny bit of imprecisions for colinear linedefs.
	}
	else
	{
		ClipDistance0 = 1;
	}

	// clip planes used for translucency splitting
	ClipDistance1 = worldcoord.y - uClipSplit.x;
	ClipDistance2 = uClipSplit.y - worldcoord.y;

	if (uSplitTopPlane == vec4(0.0))
	{
		ClipDistance3 = 1.0;
		ClipDistance4 = 1.0;
	}

#ifdef NO_CLIPDISTANCE_SUPPORT
	ClipDistanceA = vec4(ClipDistance0, ClipDistance1, ClipDistance2, ClipDistance3);
	ClipDistanceB = vec4(ClipDistance4, 0.0, 0.0, 0.0);
#else
	gl_ClipDistance[0] = ClipDistance0;
	gl_ClipDistance[1] = ClipDistance1;
	gl_ClipDistance[2] = ClipDistance2;
	gl_ClipDistance[3] = ClipDistance3;
	gl_ClipDistance[4] = ClipDistance4;
#endif

	gl_PointSize = 1.0;
}

#if !defined(SIMPLE)
vec3 GetAttrNormal()
{
	#ifdef HAS_UNIFORM_VERTEX_DATA
		if ((useVertexData & 2) == 0)
			return uVertexNormal.xyz;
		else
			return mix(aNormal.xyz, aNormal2.xyz, uInterpolationFactor);
	#else
		return mix(aNormal.xyz, aNormal2.xyz, uInterpolationFactor);
	#endif
}

void AddWeightedBone(uint boneIndex, float weight, inout vec4 position, inout vec3 normal)
{
	if (weight != 0.0)
	{
		mat4 transform = bones[uBoneIndexBase + int(boneIndex)];
		mat3 rotation = mat3(transform);
		position += (transform * aPosition) * weight;
		normal += (rotation * aNormal.xyz) * weight;
	}
}

BonesResult ApplyBones()
{
	BonesResult result;
	if (uBoneIndexBase >= 0 && aBoneWeight != vec4(0.0))
	{
		result.Position = vec4(0.0);
		result.Normal = vec3(0.0);

		// We use low precision input for our bone weights. Rescale so the sum still is 1.0
		float totalWeight = aBoneWeight.x + aBoneWeight.y + aBoneWeight.z + aBoneWeight.w;
		float weightMultiplier = 1.0 / totalWeight;
		vec4 boneWeight = aBoneWeight * weightMultiplier;

		AddWeightedBone(aBoneSelector.x, boneWeight.x, result.Position, result.Normal);
		AddWeightedBone(aBoneSelector.y, boneWeight.y, result.Position, result.Normal);
		AddWeightedBone(aBoneSelector.z, boneWeight.z, result.Position, result.Normal);
		AddWeightedBone(aBoneSelector.w, boneWeight.w, result.Position, result.Normal);

		result.Position.w = 1.0; // For numerical stability
	}
	else
	{
		result.Position = aPosition;
		result.Normal = GetAttrNormal();
	}
	return result;
}

#else

BonesResult ApplyBones()
{
	BonesResult result;
	result.Position = aPosition;
	return result;
}

#endif
