//-----------------------------------------------------------------------------
// Copyright (c) 2010-2012 dhpoware. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//
// This application demonstrates how to implement parallax normal mapping for
// a single directional light source using OpenGL 3.1 or a forward compatible
// OpenGL 3.0 rendering context. The shaders can be extended to support
// multiple directional light sources.
//
// Parallax mapping is a method for approximating the correct appearance of
// uneven surfaces by modifying the texture coordinate for each pixel. Normal
// mapping is commonly used to provide surfaces with very detailed shading
// without requiring additional geometry to be added to the polygonal model.
// However such normal mapped surfaces often still appear flat. Parallax
// mapping corrects this by providing the illusion of depth to such surfaces.
// Parallax mapping is commonly used in conjunction with normal mapping because
// parallax mapping is a relatively inexpensive means to enhance the normal
// mapping effect.
//
// The original idea for parallax mapping came from a paper by Tomomichi Kaneko
// et al (2001) titled "Detailed Shape Representation with Parallax Mapping".
// Later Terry Welsh (2004) introduced an offset limiting term to the original
// parallax mapping technique in his paper titled "Parallax Mapping with Offset
// Limiting: A PerPixel Approximation of Uneven Surfaces".
//
// Terry Welsh popularized parallax mapping. In 2004 he released a working
// implementation of this technique to the OpenGL developer community. Within
// days of posting his code onto the OpenGL.org forums many developers across
// the world had already added this technique to their own applications.
//
// Terry Welsh's parallax mapping with offset limiting paper can be found here:
// http://web.archive.org/web/20060207121301/http://www.infiscape.com/doc/parallax_mapping.pdf
//
//-----------------------------------------------------------------------------

#include <windows.h>
#include <stdexcept>
#include "application.h"
#include "mathlib.h"
#include "shader.h"
#include "texture.h"

using namespace dhpoware;

class Demo : public GL3Application
{
public:
	Demo();
    virtual ~Demo();

protected:
	virtual void init(int argc, char *argv[]);
	virtual void render();
	virtual void shutdown();
	virtual void update(float elapsedTimeSec);

private:
	struct UnitCube
	{
		struct Vertex
		{
			float pos[3];
			float normal[3];
			float texCoord[2];
			float tangent[4];
		};

		static const int TOTAL_VERTICES = 36;
		static Vertex vertices[TOTAL_VERTICES];

		Texture2D nullColorMapTexture;
		Texture2D colorMapTexture;
		Texture2D glossMapTexture;
		Texture2D normalMapTexture;
		Texture2D heightMapTexture;

		GLuint vertexArrayObject;
		GLuint vertexBufferObject;

		float scaleBias[2];
	};

	void calcTangentVector(const float pos1[3], const float pos2[3],
		                   const float pos3[3], const float texCoord1[2],
		                   const float texCoord2[2], const float texCoord3[2],
		                   const float normal[3], float tangent[4]) const;
	void initParallaxNormalMapping(Shader &shader);
	void initParallaxNormalMappingShaderProgram();
	void initParallaxNormalMappingTextures();
	void lookAt(const Vector3 &eye, const Vector3 &target, const Vector3 &up);
	const float *normalMatrix();
	void perspective(float fovx, float aspect, float znear, float zfar);
	void processKeyboard();
	void processMouse();
	void rotate(float degrees, float x, float y, float z);
	void unitCubeCreate(Shader &shader);
	void unitCubeAddFace(int index, float v1[3], float v2[3], float v3[3], float v4[3],
		                 float n[3], float tc1[2], float tc2[2], float tc3[2], float tc4[2]);
	void unitCubeDestroy();
	void unitCubeRender(Shader &shader);

	static const float UNIT_CUBE_ROTATION_SPEED;

	int m_mousePosPrevX;
	int m_mousePosPrevY;
	Shader m_parallaxNormalMappingShader;
	Matrix4 m_projMatrix;
	Matrix4 m_viewMatrix;
	Matrix4 m_modelMatrix;
	Matrix3 m_normalMatrix;
	Quaternion m_cubeRotation;
	UnitCube m_cube;
	bool m_colorMapDisabled;
};

//-----------------------------------------------------------------------------
// Demo class implementation.
//-----------------------------------------------------------------------------

const float Demo::UNIT_CUBE_ROTATION_SPEED = 0.4f;

Demo::UnitCube::Vertex Demo::UnitCube::vertices[Demo::UnitCube::TOTAL_VERTICES];

Demo::Demo() : GL3Application("OpenGL 3 Parallax Normal Mapping with a directional light")
{
	m_cubeRotation.identity();
	m_colorMapDisabled = false;
}

Demo::~Demo()
{
}

void Demo::init(int argc, char *argv[])
{
	initParallaxNormalMappingShaderProgram();
	initParallaxNormalMappingTextures();
	initParallaxNormalMapping(m_parallaxNormalMappingShader);
}

void Demo::initParallaxNormalMapping(Shader &shader)
{
	// Build the cube's geometry.

	unitCubeCreate(m_parallaxNormalMappingShader);

	// Setup initial parallax scale and bias values for the shader.
	// These are the suggested good initial values that Terry Welsh uses in
	// his paper and implementation.

	m_cube.scaleBias[0] = 0.04f;
	m_cube.scaleBias[1] = -0.03f;

	// Setup the shader.

	shader.enable();

	shader.uniform1("ColorMapSampler", 0);
	shader.uniform1("GlossMapSampler", 1);
	shader.uniform1("NormalMapSampler", 2);
	shader.uniform1("HeightMapSampler", 3);

	// Setup a directional light that is shining down the negative z world axis.

	shader.uniform3("DirectionalLight1.direction", 0.0f, 0.0f, -1.0f);
	shader.uniform4("DirectionalLight1.ambient", 1.0f, 1.0f, 1.0f, 1.0f);
	shader.uniform4("DirectionalLight1.diffuse", 1.0f, 1.0f, 1.0f, 1.0f);
	shader.uniform4("DirectionalLight1.specular", 1.0f, 1.0f, 1.0f, 1.0f);

	// Setup a basic Phong material.

	shader.uniform4("Material1.ambient", 0.3f, 0.3f, 0.3f, 1.0f);
	shader.uniform4("Material1.diffuse", 0.7f, 0.7f, 0.7f, 1.0f);
	shader.uniform4("Material1.specular", 0.5f, 0.5f, 0.5f, 1.0f);
	shader.uniform1("Material1.shininess", 60.0f);

	shader.disable();
}

void Demo::initParallaxNormalMappingShaderProgram()
{
	if (!m_parallaxNormalMappingShader.loadVertexShader("Content\\Shaders\\parallax_normal_mapping_dir.vert"))
		throw std::runtime_error(m_parallaxNormalMappingShader.lastError());

	if (!m_parallaxNormalMappingShader.loadFragmentShader("Content\\Shaders\\parallax_normal_mapping_dir.frag"))
		throw std::runtime_error(m_parallaxNormalMappingShader.lastError());

	if (!m_parallaxNormalMappingShader.link())
		throw std::runtime_error(m_parallaxNormalMappingShader.lastError());
}

void Demo::initParallaxNormalMappingTextures()
{
	if (!(m_cube.nullColorMapTexture.load("Content\\Textures\\white.png")))
		throw std::runtime_error("Unable to load texture: white.png");

	if (!(m_cube.colorMapTexture.load("Content\\Textures\\floor_tile_color_map.png", GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE)))
		throw std::runtime_error("Unable to load texture: floor_tile_color_map.png");

	if (!(m_cube.glossMapTexture.load("Content\\Textures\\floor_tile_gloss_map.png", GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE)))
		throw std::runtime_error("Unable to load texture: floor_tile_gloss_map.png");

	if (!(m_cube.normalMapTexture.load("Content\\Textures\\floor_tile_normal_map.png", GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE)))
		throw std::runtime_error("Unable to load texture: floor_tile_normal_map.png");

	if (!(m_cube.heightMapTexture.load("Content\\Textures\\floor_tile_height_map.png", GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE)))
		throw std::runtime_error("Unable to load texture: floor_tile_height_map.png");
}

void Demo::render()
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);

	glViewport(0, 0, viewportWidth(), viewportHeight());
	glClearColor(0.3f, 0.5f, 0.9f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	
	unitCubeRender(m_parallaxNormalMappingShader);
}

void Demo::shutdown()
{
	unitCubeDestroy();
	m_parallaxNormalMappingShader.destroy();
}

void Demo::update(float elapsedTimeSec)
{
	processKeyboard();
	processMouse();
}

void Demo::calcTangentVector(const float pos1[3], const float pos2[3],
							 const float pos3[3], const float texCoord1[2],
							 const float texCoord2[2], const float texCoord3[2],
							 const float normal[3], float tangent[4]) const
{
	// Given the 3 vertices (position and texture coordinates) of a triangle
	// calculate and return the triangle's tangent vector.

	// Create 2 vectors in object space.
	//
	// edge1 is the vector from vertex positions pos1 to pos2.
	// edge2 is the vector from vertex positions pos1 to pos3.
	Vector3 edge1(pos2[0] - pos1[0], pos2[1] - pos1[1], pos2[2] - pos1[2]);
	Vector3 edge2(pos3[0] - pos1[0], pos3[1] - pos1[1], pos3[2] - pos1[2]);

	edge1.normalize();
	edge2.normalize();

	// Create 2 vectors in tangent (texture) space that point in the same
	// direction as edge1 and edge2 (in object space).
	//
	// texEdge1 is the vector from texture coordinates texCoord1 to texCoord2.
	// texEdge2 is the vector from texture coordinates texCoord1 to texCoord3.
	Vector2 texEdge1(texCoord2[0] - texCoord1[0], texCoord2[1] - texCoord1[1]);
	Vector2 texEdge2(texCoord3[0] - texCoord1[0], texCoord3[1] - texCoord1[1]);

	texEdge1.normalize();
	texEdge2.normalize();

	// These 2 sets of vectors form the following system of equations:
	//
	//  edge1 = (texEdge1.x * tangent) + (texEdge1.y * bitangent)
	//  edge2 = (texEdge2.x * tangent) + (texEdge2.y * bitangent)
	//
	// Using matrix notation this system looks like:
	//
	//  [ edge1 ]     [ texEdge1.x  texEdge1.y ]  [ tangent   ]
	//  [       ]  =  [                        ]  [           ]
	//  [ edge2 ]     [ texEdge2.x  texEdge2.y ]  [ bitangent ]
	//
	// The solution is:
	//
	//  [ tangent   ]        1     [ texEdge2.y  -texEdge1.y ]  [ edge1 ]
	//  [           ]  =  -------  [                         ]  [       ]
	//  [ bitangent ]      det A   [-texEdge2.x   texEdge1.x ]  [ edge2 ]
	//
	//  where:
	//        [ texEdge1.x  texEdge1.y ]
	//    A = [                        ]
	//        [ texEdge2.x  texEdge2.y ]
	//
	//    det A = (texEdge1.x * texEdge2.y) - (texEdge1.y * texEdge2.x)
	//
	// From this solution the tangent space basis vectors are:
	//
	//    tangent = (1 / det A) * ( texEdge2.y * edge1 - texEdge1.y * edge2)
	//  bitangent = (1 / det A) * (-texEdge2.x * edge1 + texEdge1.x * edge2)
	//     normal = cross(tangent, bitangent)

	Vector3 t;
	Vector3 b;
	Vector3 n(normal[0], normal[1], normal[2]);

	float det = (texEdge1.x * texEdge2.y) - (texEdge1.y * texEdge2.x);

	if (Math::closeEnough(det, 0.0f))
	{
		t.set(1.0f, 0.0f, 0.0f);
		b.set(0.0f, 1.0f, 0.0f);
	}
	else
	{
		det = 1.0f / det;

		t.x = (texEdge2.y * edge1.x - texEdge1.y * edge2.x) * det;
		t.y = (texEdge2.y * edge1.y - texEdge1.y * edge2.y) * det;
		t.z = (texEdge2.y * edge1.z - texEdge1.y * edge2.z) * det;

		b.x = (-texEdge2.x * edge1.x + texEdge1.x * edge2.x) * det;
		b.y = (-texEdge2.x * edge1.y + texEdge1.x * edge2.y) * det;
		b.z = (-texEdge2.x * edge1.z + texEdge1.x * edge2.z) * det;

		t.normalize();
		b.normalize();
	}

	// Calculate the handedness of the local tangent space.
	// The bitangent vector is the cross product between the triangle face
	// normal vector and the calculated tangent vector. The resulting bitangent
	// vector should be the same as the bitangent vector calculated from the
	// set of linear equations above. If they point in different directions
	// then we need to invert the cross product calculated bitangent vector. We
	// store this scalar multiplier in the tangent vector's 'w' component so
	// that the correct bitangent vector can be generated in the normal mapping
	// shader's vertex shader.

	Vector3 bitangent = Vector3::cross(n, t);
	float handedness = (Vector3::dot(bitangent, b) < 0.0f) ? -1.0f : 1.0f;

	tangent[0] = t.x;
	tangent[1] = t.y;
	tangent[2] = t.z;
	tangent[3] = handedness;
}

void Demo::lookAt(const Vector3 &eye, const Vector3 &target, const Vector3 &up)
{
	// Builds a look-at style view matrix.
	// This is essentially the same matrix used by gluLookAt().

	Vector3 zAxis = eye - target;
	zAxis.normalize();

	Vector3 xAxis = Vector3::cross(up, zAxis);
	xAxis.normalize();

	Vector3 yAxis = Vector3::cross(zAxis, xAxis);
	yAxis.normalize();

	m_viewMatrix[0][0] = xAxis.x;
	m_viewMatrix[1][0] = xAxis.y;
	m_viewMatrix[2][0] = xAxis.z;
	m_viewMatrix[3][0] = -Vector3::dot(xAxis, eye);

	m_viewMatrix[0][1] = yAxis.x;
	m_viewMatrix[1][1] = yAxis.y;
	m_viewMatrix[2][1] = yAxis.z;
	m_viewMatrix[3][1] = -Vector3::dot(yAxis, eye);

	m_viewMatrix[0][2] = zAxis.x;
	m_viewMatrix[1][2] = zAxis.y;
	m_viewMatrix[2][2] = zAxis.z;
	m_viewMatrix[3][2] = -Vector3::dot(zAxis, eye);

	m_viewMatrix[0][3] = 0.0f;
	m_viewMatrix[1][3] = 0.0f;
	m_viewMatrix[2][3] = 0.0f;
	m_viewMatrix[3][3] = 1.0f;
}

const float *Demo::normalMatrix()
{
	// The normal matrix is the transpose of the inverse of the upper 3x3
	// sub-matrix of the model-view matrix. It is used to transform normals
	// from model space into eye space.

	Matrix4 modelViewMatrix = m_modelMatrix * m_viewMatrix;

	m_normalMatrix[0][0] = modelViewMatrix[0][0];
	m_normalMatrix[0][1] = modelViewMatrix[0][1];
	m_normalMatrix[0][2] = modelViewMatrix[0][2];

	m_normalMatrix[1][0] = modelViewMatrix[1][0];
	m_normalMatrix[1][1] = modelViewMatrix[1][1];
	m_normalMatrix[1][2] = modelViewMatrix[1][2];

	m_normalMatrix[2][0] = modelViewMatrix[2][0];
	m_normalMatrix[2][1] = modelViewMatrix[2][1];
	m_normalMatrix[2][2] = modelViewMatrix[2][2];

	m_normalMatrix = m_normalMatrix.inverse().transpose();

	return &m_normalMatrix[0][0];
}

void Demo::perspective(float fovx, float aspect, float znear, float zfar)
{
	// Construct a projection matrix based on the horizontal field of view
	// 'fovx' rather than the more traditional vertical field of view 'fovy'.

	float e = 1.0f / tanf(Math::degreesToRadians(fovx) / 2.0f);
	float aspectInv = 1.0f / aspect;
	float fovy = 2.0f * atanf(aspectInv / e);
	float xScale = 1.0f / tanf(0.5f * fovy);
	float yScale = xScale / aspectInv;

	m_projMatrix[0][0] = xScale;
	m_projMatrix[0][1] = 0.0f;
	m_projMatrix[0][2] = 0.0f;
	m_projMatrix[0][3] = 0.0f;

	m_projMatrix[1][0] = 0.0f;
	m_projMatrix[1][1] = yScale;
	m_projMatrix[1][2] = 0.0f;
	m_projMatrix[1][3] = 0.0f;

	m_projMatrix[2][0] = 0.0f;
	m_projMatrix[2][1] = 0.0f;
	m_projMatrix[2][2] = (zfar + znear) / (znear - zfar);
	m_projMatrix[2][3] = -1.0f;

	m_projMatrix[3][0] = 0.0f;
	m_projMatrix[3][1] = 0.0f;
	m_projMatrix[3][2] = (2.0f * zfar * znear) / (znear - zfar);
	m_projMatrix[3][3] = 0.0f;
}

void Demo::processKeyboard()
{
	if (keyDown(Keyboard::KEY_LALT) || keyDown(Keyboard::KEY_RALT))
	{
		if (keyPressed(Keyboard::KEY_ENTER))
			toggleFullScreen();
	}

	if (keyPressed(Keyboard::KEY_ESCAPE))
		quit();

	if (keyPressed(Keyboard::KEY_SPACE))
		m_colorMapDisabled = !m_colorMapDisabled;
}

void Demo::processMouse()
{
	const Mouse &m = mouse();
	int x = 0;
	int y = 0;
	float dx = 0.0f;
	float dy = 0.0f;

	if (m.buttonPressed(Mouse::BUTTON_LEFT))
	{
		m_mousePosPrevX = m.xPosAbsolute();
		m_mousePosPrevY = m.yPosAbsolute();
	}
	else if (m.buttonDown(Mouse::BUTTON_LEFT))
	{
		x = m.xPosAbsolute();
		y = m.yPosAbsolute();

		dx = (x - m_mousePosPrevX) * UNIT_CUBE_ROTATION_SPEED;
		dy = (y - m_mousePosPrevY) * UNIT_CUBE_ROTATION_SPEED;

		if (dx)
			m_cubeRotation = Quaternion(Vector3(0.0f, 1.0f, 0.0f), dx) * m_cubeRotation;

		if (dy)
			m_cubeRotation = m_cubeRotation * Quaternion(Vector3(1.0f, 0.0f, 0.0f), dy);

		m_mousePosPrevX = x;
		m_mousePosPrevY = y;
	}
}

void Demo::rotate(float degrees, float x, float y, float z)
{
	m_modelMatrix = Matrix4::createRotate(Vector3(x, y, z), degrees);
}

void Demo::unitCubeCreate(Shader &shader)
{
	/*
			   H--------G
			  /|       /|
			 / |      / |
			D--|-----C  |
			|  E-----|--F
			| /      | /
			|/       |/
			A--------B
		Y
		|
		|
		o----X
       /
      /
     Z
			 D-------C
			 |      /|
			 |    /  |
			 |  /    |
			 |/      |
			 A-------B

			      face: ABCD
			 triangles: ABC ACD

		+x face: BFGC triangles: BFG BGC
		-x face: EADH triangles: EAD EDH

		+y face: DCGH triangles: DCG DGH
		-y face: EFBA triangles: EFB EBA

		+z face: ABCD triangles: ABC ACD
		-z face: FEHG triangles: FEH FHG

	*/

	float corners[8][3] =
	{
		-0.5f, -0.5f,  0.5f,	// A
		 0.5f, -0.5f,  0.5f,	// B
		 0.5f,  0.5f,  0.5f,	// C
		-0.5f,  0.5f,  0.5f,	// D

		-0.5f, -0.5f, -0.5f,	// E
		 0.5f, -0.5f, -0.5f,	// F
		 0.5f,  0.5f, -0.5f,	// G
		-0.5f,  0.5f, -0.5f,	// H
	};

	float normals[6][3] =
	{
		 1.0f,  0.0f,  0.0f,	// +x face
		-1.0f,  0.0f,  0.0f,	// -x face
		 0.0f,  1.0f,  0.0f,	// +y face
		 0.0f, -1.0f,  0.0f,	// -y face
		 0.0f,  0.0f,  1.0f,	// +z face
		 0.0f,  0.0f, -1.0f		// -z face
	};

	float texCoords[4][2] =
	{
		0.0f, 0.0f,				// lower left
		1.0f, 0.0f,				// lower right
		1.0f, 1.0f,				// upper right
		0.0f, 1.0f				// upper left
	};

	unitCubeAddFace( 0, corners[1], corners[5], corners[6], corners[2], normals[0], texCoords[0], texCoords[1], texCoords[2], texCoords[3]); // +x face
	unitCubeAddFace( 6, corners[4], corners[0], corners[3], corners[7], normals[1], texCoords[0], texCoords[1], texCoords[2], texCoords[3]); // -x face
	unitCubeAddFace(12, corners[3], corners[2], corners[6], corners[7], normals[2], texCoords[0], texCoords[1], texCoords[2], texCoords[3]); // +y face
	unitCubeAddFace(18, corners[4], corners[5], corners[1], corners[0], normals[3], texCoords[0], texCoords[1], texCoords[2], texCoords[3]); // -y face
	unitCubeAddFace(24, corners[0], corners[1], corners[2], corners[3], normals[4], texCoords[0], texCoords[1], texCoords[2], texCoords[3]); // +z face
	unitCubeAddFace(30, corners[5], corners[4], corners[7], corners[6], normals[5], texCoords[0], texCoords[1], texCoords[2], texCoords[3]); // -z face

	// Create the vertex array object for the cube.
	//
	// The buffer objects that are to be used by the vertex stage of the GL
	// are collected together to form a vertex array object. All state related
	// to the definition of data used by the vertex processor is encapsulated
	// in a vertex array object.
	//
	// Vertex array objects maintain state. Any required vertex and index
	// buffer objects are created and bound to the vertex array object. Then
	// any vertex shader generic shader input variables are mapped to the
	// vertex data stored in the vertex buffer objects bound to the vertex
	// array object. All of this only needs to be done once when using vertex
	// array objects.
	//
	// When we want to draw the cube we simple bind the vertex array object and
	// then call glDrawArrays() to do the drawing. There is no need to define
	// the cube's vertex array format again since that information has already
	// been specified and saved to the vertex array object.
	//
	// See the unitCubeRender() method to see how vertex array objects are used
	// in practice.

	glGenVertexArrays(1, &m_cube.vertexArrayObject);
	glBindVertexArray(m_cube.vertexArrayObject);

	// Create the Vertex Buffer Object for the cube.

	glGenBuffers(1, &m_cube.vertexBufferObject);
	glBindBuffer(GL_ARRAY_BUFFER, m_cube.vertexBufferObject);
	glBufferData(GL_ARRAY_BUFFER, sizeof(UnitCube::vertices), UnitCube::vertices, GL_STATIC_DRAW);

	// GLSL 1.30 (OpenGL 3.0) deprecates varying and attribute shader
	// variables. These are replaced with used-defined generic vertex and
	// fragment shader input and output variables.
	//
	// Map the generic vertex attributes used by the cube's shader program to
	// the cube's vertex data stored in the vertex buffer object.

	shader.vertexAttribPointer("VertexPosition", 3, GL_FLOAT, GL_FALSE, sizeof(UnitCube::Vertex), BUFFER_OFFSET(0));
	shader.enableVertexAttribArray("VertexPosition");

	shader.vertexAttribPointer("VertexNormal", 3, GL_FLOAT, GL_FALSE, sizeof(UnitCube::Vertex), BUFFER_OFFSET(sizeof(float) * 3));
	shader.enableVertexAttribArray("VertexNormal");

	shader.vertexAttribPointer("VertexTexCoord", 2, GL_FLOAT, GL_FALSE, sizeof(UnitCube::Vertex), BUFFER_OFFSET(sizeof(float) * 6));
	shader.enableVertexAttribArray("VertexTexCoord");

	shader.vertexAttribPointer("VertexTangent", 4, GL_FLOAT, GL_FALSE, sizeof(UnitCube::Vertex), BUFFER_OFFSET(sizeof(float) * 8));
	shader.enableVertexAttribArray("VertexTangent");
}

void Demo::unitCubeAddFace(int index, float v1[3], float v2[3], float v3[3], float v4[3], float n[3], float tc1[2], float tc2[2], float tc3[2], float tc4[2])
{
	UnitCube::Vertex *pVertex = &UnitCube::vertices[index];

	// Set the vertex attributes for the 2 triangles that make up this face.

	pVertex[0].pos[0] = v1[0];
	pVertex[0].pos[1] = v1[1];
	pVertex[0].pos[2] = v1[2];
	pVertex[0].normal[0] = n[0];
	pVertex[0].normal[1] = n[1];
	pVertex[0].normal[2] = n[2];
	pVertex[0].texCoord[0] = tc1[0];
	pVertex[0].texCoord[1] = tc1[1];

	pVertex[1].pos[0] = v2[0];
	pVertex[1].pos[1] = v2[1];
	pVertex[1].pos[2] = v2[2];
	pVertex[1].normal[0] = n[0];
	pVertex[1].normal[1] = n[1];
	pVertex[1].normal[2] = n[2];
	pVertex[1].texCoord[0] = tc2[0];
	pVertex[1].texCoord[1] = tc2[1];

	pVertex[2].pos[0] = v3[0];
	pVertex[2].pos[1] = v3[1];
	pVertex[2].pos[2] = v3[2];
	pVertex[2].normal[0] = n[0];
	pVertex[2].normal[1] = n[1];
	pVertex[2].normal[2] = n[2];
	pVertex[2].texCoord[0] = tc3[0];
	pVertex[2].texCoord[1] = tc3[1];

	pVertex[3].pos[0] = v1[0];
	pVertex[3].pos[1] = v1[1];
	pVertex[3].pos[2] = v1[2];
	pVertex[3].normal[0] = n[0];
	pVertex[3].normal[1] = n[1];
	pVertex[3].normal[2] = n[2];
	pVertex[3].texCoord[0] = tc1[0];
	pVertex[3].texCoord[1] = tc1[1];

	pVertex[4].pos[0] = v3[0];
	pVertex[4].pos[1] = v3[1];
	pVertex[4].pos[2] = v3[2];
	pVertex[4].normal[0] = n[0];
	pVertex[4].normal[1] = n[1];
	pVertex[4].normal[2] = n[2];
	pVertex[4].texCoord[0] = tc3[0];
	pVertex[4].texCoord[1] = tc3[1];

	pVertex[5].pos[0] = v4[0];
	pVertex[5].pos[1] = v4[1];
	pVertex[5].pos[2] = v4[2];
	pVertex[5].normal[0] = n[0];
	pVertex[5].normal[1] = n[1];
	pVertex[5].normal[2] = n[2];
	pVertex[5].texCoord[0] = tc4[0];
	pVertex[5].texCoord[1] = tc4[1];

	// Calculate the tangent vector for this face.
	// Since the cube's face is planar we can use the same tangent vector for
	// each of the 6 vertices of the 2 triangles that make up this face.

	float tangent[4] = {0.0f, 0.0f, 0.0f, 0.0f};

	calcTangentVector(pVertex[0].pos, pVertex[1].pos, pVertex[2].pos,
		              pVertex[0].texCoord, pVertex[1].texCoord, pVertex[2].texCoord,
					  n, tangent);

	pVertex[0].tangent[0] = tangent[0];
	pVertex[0].tangent[1] = tangent[1];
	pVertex[0].tangent[2] = tangent[2];
	pVertex[0].tangent[3] = tangent[3];

	pVertex[1].tangent[0] = tangent[0];
	pVertex[1].tangent[1] = tangent[1];
	pVertex[1].tangent[2] = tangent[2];
	pVertex[1].tangent[3] = tangent[3];

	pVertex[2].tangent[0] = tangent[0];
	pVertex[2].tangent[1] = tangent[1];
	pVertex[2].tangent[2] = tangent[2];
	pVertex[2].tangent[3] = tangent[3];

	pVertex[3].tangent[0] = tangent[0];
	pVertex[3].tangent[1] = tangent[1];
	pVertex[3].tangent[2] = tangent[2];
	pVertex[3].tangent[3] = tangent[3];

	pVertex[4].tangent[0] = tangent[0];
	pVertex[4].tangent[1] = tangent[1];
	pVertex[4].tangent[2] = tangent[2];
	pVertex[4].tangent[3] = tangent[3];

	pVertex[5].tangent[0] = tangent[0];
	pVertex[5].tangent[1] = tangent[1];
	pVertex[5].tangent[2] = tangent[2];
	pVertex[5].tangent[3] = tangent[3];
}

void Demo::unitCubeDestroy()
{
	if (m_cube.vertexArrayObject)
	{
		glDeleteVertexArrays(1, &m_cube.vertexArrayObject);
		m_cube.vertexArrayObject = 0;
	}

	if (m_cube.vertexBufferObject)
	{
		glDeleteBuffers(1, &m_cube.vertexBufferObject);
		m_cube.vertexBufferObject = 0;
	}

	m_cube.colorMapTexture.destroy();
	m_cube.glossMapTexture.destroy();
	m_cube.heightMapTexture.destroy();
	m_cube.normalMapTexture.destroy();
	m_cube.nullColorMapTexture.destroy();
}

void Demo::unitCubeRender(Shader &shader)
{
	float rotationDegrees = 0.0f;
	Vector3 rotationAxis(0.0f, 0.0f, 0.0f);

	m_cubeRotation.toAxisAngle(rotationAxis, rotationDegrees);
	rotate(rotationDegrees, rotationAxis.x, rotationAxis.y, rotationAxis.z);

	perspective(90.0f, float(viewportWidth()) / float(viewportHeight()), 1.0f, 20.0f);
	lookAt(Vector3(0.0f, 0.0f, 2.5f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 1.0f, 0.0f));

	shader.enable();

	shader.uniformMatrix4("ProjectionMatrix", 1, GL_FALSE, &m_projMatrix[0][0]);
	shader.uniformMatrix4("ViewMatrix", 1, GL_FALSE, &m_viewMatrix[0][0]);
	shader.uniformMatrix4("ModelMatrix", 1, GL_FALSE, &m_modelMatrix[0][0]);
	shader.uniformMatrix3("NormalMatrix", 1, GL_FALSE, normalMatrix());
	shader.uniform2("ParallaxScaleBias", 1, m_cube.scaleBias);

	glActiveTexture(GL_TEXTURE0);
	if (m_colorMapDisabled)
		glBindTexture(GL_TEXTURE_2D, m_cube.nullColorMapTexture.textureObject());
	else
		glBindTexture(GL_TEXTURE_2D, m_cube.colorMapTexture.textureObject());

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, m_cube.glossMapTexture.textureObject());

	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, m_cube.normalMapTexture.textureObject());

	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, m_cube.heightMapTexture.textureObject());

	glBindVertexArray(m_cube.vertexArrayObject);
	glDrawArrays(GL_TRIANGLES, 0, UnitCube::TOTAL_VERTICES);
	glBindVertexArray(0);

	glBindTexture(GL_TEXTURE_2D, 0);

	shader.disable();
}

//-----------------------------------------------------------------------------
// WinMain - application entry point.
//-----------------------------------------------------------------------------

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    Demo demo;
    
	demo.preferAnisotropicTextureFiltering();
    demo.preferAntiAliasing();
    demo.preferWindowed();

    return demo.run();
}