#include "demo_shadow.h"

#include "scenenode.h"

#include <glm/gtx/transform.hpp>
// glm::perspective
#include <glm/gtc/matrix_projection.hpp>
// glm::translate, glm::rotate, glm::scale
#include <glm/gtc/matrix_transform.hpp>
// glm::value_ptr
#include <glm/gtc/type_ptr.hpp> 

#include "transformation_node.h"
#include <ctime>

demo_shadow::demo_shadow()
{
	poly_offset_factor = 1.1;
	poly_offset_units = 4.0;
	width = 800;
	height = 600;
	near_far = glm::vec2(0.1f, 200.0f);
	load_config();
	perspective_matrix = glm::perspective(45.0f, static_cast<GLfloat>(width) / static_cast<GLfloat>(height), near_far.x, near_far.y);
	title = "Shadowmapping test";
	cubemap_dim = 800;
	view_matrix = glm::mat4(1.0f);
}

demo_shadow::~demo_shadow()
{
}

bool demo_shadow::init()
{	
	if (!init_window())
	{
		return false;
	}

	//load textures
	if (!load_tga_texture(&texture[BG], "../../textures/bg1.tga", GL_SRGB8_ALPHA8) 
		)
	{
		std::cout << "failed to load textures!\n";
		return false;
	}
	if (!load_tga_texture(&texture[TREE], "../../textures/bark11_2.tga", GL_SRGB8_ALPHA8))
	{
		std::cout << "failed to load textures!\n";
		return false;
	}
	if (!load_tga_texture(&texture[YELLOW], "../../textures/yellow.tga", GL_SRGB8_ALPHA8))
	{
		std::cout << "failed to load textures!\n";
		return false;
	}
	if (!load_tga_texture(&texture[STONE], "../../textures/stone09.tga", GL_SRGB8_ALPHA8))
	{
		std::cout << "failed to load textures!\n";
		return false;
	}

	//cam
	cam_eye = glm::vec3(-15.0f, 3.0f, 20.f);
	cam_center = glm::vec3(0.0f, 0.0f, 0.0f);
	cam_up = glm::vec3(0.0f, 1.0f, 0.0f);

	//light
	light_0 = glm::vec3(0.0f, 3.0f, 0.0f);

	//do opengl initialization here
	glClearColor(0.3f, 0.3f, 0.3f, 0.5f);
	glEnable(GL_DEPTH_TEST);
	glClearDepth(1.0f);
	glDepthFunc(GL_LEQUAL);
	//glEnable(GL_CULL_FACE);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	// Load and compile Shader files	
	texture_shader = std::auto_ptr<Shader>( new Shader ("../../shader/texture_shadowmap") );
	if (!(*texture_shader)) {
		cerr << "Could not compile sandbox shader program." << endl;
		return false;
	}

	texture_shader->bind_frag_data_location("out_color");

	
	// Load and compile Shader files	
	shadowmap_gen = std::auto_ptr<Shader>( new Shader ("../../shader/shadowmap_gen") );
	if (!(*shadowmap_gen)) {
		cerr << "Could not compile sandbox shader program." << endl;
		return false;
	}

	shadowmap_gen->bind_frag_data_location("out_color");

	//wavefront object model setup
	if (object_model.loadMod("../../models/Tree3.obj"))
	{
		cout << "object model loading failed\n";
		//cube.obj, cauldron_Scene
		return false;
	}	

	if (object_model2.loadMod("../../models/can.obj"))
	{
		cout << "object model loading failed\n";
		//cube.obj, cauldron_Scene
		return false;
	}

	if (cube.loadMod("../../models/cube2.obj"))
	{
		cout << "object model loading failed\n";
		//cube.obj, cauldron_Scene
		return false;
	}

	texture_shader->bind();

	shadow_r2cm = std::auto_ptr<geometry_shader>( new geometry_shader("../../shader/shadow_r2cm") );
	if (!(*shadow_r2cm)) {
		cerr << "Could not compile shadow_r2cm shader program." << endl;
		return false;
	}
	//shadow_r2cm->bind_frag_data_location("out_color");

	texture_odsm = std::auto_ptr<Shader>( new Shader ("../../shader/texture_odsm") );
	if (!(*texture_odsm)) {
		cerr << "Could not compile texure_odsm shader program." << endl;
		return false;
	}
	texture_odsm->bind_frag_data_location("out_color");
	
	std::cout << "build bg vbos:\n";
	init_bg(*texture_odsm, bg_vbo_id, &bg_vao_id);
	std::cout << "build tree vbos:\n";
	object_model.buildVBOs(*texture_odsm);
	object_model2.buildVBOs(*texture_odsm);
	cube.buildVBOs(*texture_odsm);
	
	get_errors();
	
	texture_shader->unbind();	
	
	shadow_r2cm->bind();
	
	glBindVertexArray(object_model.getVAO());
	glBindBuffer(GL_ARRAY_BUFFER, object_model.getVBO_vertices());
	GLint vertex_location = shadow_r2cm->get_attrib_location("in_position");
	glEnableVertexAttribArray(vertex_location);
	glVertexAttribPointer(	vertex_location, 4, GL_FLOAT, GL_FALSE, 0, NULL);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object_model.getVBO_indices());

	get_errors();

	shadowmap_gen->bind();

	std::cout << "assign vertices to shadowmap shader:\n";
	//init shadowmap_gen shader
	glBindVertexArray(bg_vao_id);
	glBindBuffer(GL_ARRAY_BUFFER, bg_vbo_id[0]);
	vertex_location = shadowmap_gen->get_attrib_location("in_position");
	glEnableVertexAttribArray(vertex_location);
	glVertexAttribPointer(	vertex_location, 4, GL_FLOAT, GL_FALSE, 0, NULL);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bg_vbo_id[3]);

	
	glBindVertexArray(object_model.getVAO());
	glBindBuffer(GL_ARRAY_BUFFER, object_model.getVBO_vertices());
	vertex_location = shadowmap_gen->get_attrib_location("in_position");
	glEnableVertexAttribArray(vertex_location);
	glVertexAttribPointer(	vertex_location, 4, GL_FLOAT, GL_FALSE, 0, NULL);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object_model.getVBO_indices());


	
	std::cout << "assign vertices to shadowmap shader done \n";

	
	shadow_r2cm->unbind();

	//setup scenegraph
	bg = object_node(  glm::translate(0.0f, 0.0f, -8.0f) * glm::scale(10.0f, 10.0f, 10.0f), bg_vao_id, 6, SH_TEXTURE);
	bg.set_texture_id(texture[BG]);
	ground = object_node(  glm::translate(0.0f, -2.6f, 0.0f) * glm::rotate(-90.0f, 1.0f, 0.0f, 0.0f) * glm::scale(10.0f, 10.0f, 10.0f), bg_vao_id, 6, SH_TEXTURE);
	ground.set_texture_id(texture[BG]);
	right_side = object_node(  glm::translate(10.0f, 0.0f, 0.0f) * glm::rotate(-90.0f, 0.0f, 1.0f, 0.0f) * glm::scale(10.0f, 10.0f, 10.0f), bg_vao_id, 6, SH_TEXTURE);
	right_side.set_texture_id(texture[BG]);
	light = object_node(  glm::translate(light_0), cube.getVAO(), cube.getMesh()->getIndicesCount(), SH_TEXTURE);
	light.set_texture_id(texture[YELLOW]);
	model = object_node(glm::translate(-5.0f, 0.0f, 0.0f), object_model.getVAO(), object_model.getMesh()->getIndicesCount() , SH_TEXTURE);
	model.set_texture_id(texture[TREE]);
	model2 = object_node(glm::translate(5.0f, 0.0f, 0.0f) * glm::scale(0.5f, 0.5f, 0.5f), object_model2.getVAO(), object_model2.getMesh()->getIndicesCount() , SH_TEXTURE);
	model2.set_texture_id(texture[STONE]);

	root.add_child(object_root);
	object_root.add_child(model);
	object_root.add_child(model2);
	root.add_child(bg);
	root.add_child(ground);
	root.add_child(right_side);
	root.add_child(light);

	//init shadowmap fbo and texture
	init_odsm();
	init_offsets();
	init_shadowmap();

	return true;
}

void demo_shadow::init_shadowmap()
{
	// create the texture we'll use for the shadowmap
	glGenTextures(1, &shadow_tex_ID);
	glBindTexture(GL_TEXTURE_2D, shadow_tex_ID);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
		GL_COMPARE_REF_TO_TEXTURE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width,
	height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
	glGenFramebuffers(1, &shadow_FBO);
	glBindFramebuffer(GL_FRAMEBUFFER, shadow_FBO);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
	GL_TEXTURE_2D, shadow_tex_ID, 0);
	glDrawBuffer(GL_NONE); // essential for depth-only FBOs!!!
	glReadBuffer(GL_NONE); // essential for depth-only FBOs!!!
	

	std::cout << "framebuffer completeness check:\n";
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
		// ToDo:
		printf("GL_FRAMEBUFFER_COMPLETE error 0x%x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
	}
	std::cout << "framebuffer completeness check done\n";

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	// then, just before rendering
	//glBindFramebuffer(GL_FRAMEBUFFER, shadow_FBO);

	//uniform arrays testing
	texture_shader->bind();
	GLfloat offsets[] = {	
	0, 0,
	0, 1,
	1, 0,
	1, 1	};

	get_errors();
	GLint os_location;
	glm::vec2 bla = glm::vec2(0.0f, 0.0f);

	os_location = texture_shader->get_uniform_location("other_offsets");
	glUniform2fv(os_location, 4, offsets);
	
	texture_shader->unbind();
	
	get_errors();
}

//create cubemap for odsm
//based on RTR repetitorium 2010 slides
void demo_shadow::init_odsm()
{
	//create FBO
	glGenFramebuffers(1, &odsm_FBO);
	glBindFramebuffer(GL_FRAMEBUFFER, odsm_FBO);

		// generate cubemap texture
	glGenTextures(1, &odsm_tex_ID);
	glBindTexture(GL_TEXTURE_CUBE_MAP, odsm_tex_ID);

	//fix seam-artifacts due to numerical precision limitations
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	//unsigned 24bit int z-buffer
	//if more precision is needed go GL_DEPTH_COMPONENT32F
	GLint internal_format = GL_DEPTH_COMPONENT24;
	GLenum data_type = GL_UNSIGNED_INT;

	GLenum format = GL_DEPTH_COMPONENT;

	for (GLint face=0; face < 6; face++) {
		glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
			0,
			internal_format,
			cubemap_dim, cubemap_dim, 0,
			format,
			data_type,
			NULL // dont need to specify content
			);
	}

	//attach cubemap at FBO's depth attachment point
	glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, odsm_tex_ID, 0);

	//tell opengl we did not add a color texture on purpose
	//else the FBO is considered incomplete
	glDrawBuffer(GL_NONE);
	glReadBuffer(GL_NONE);

	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
		// ToDo:
		printf("GL_FRAMEBUFFER_COMPLETE error 0x%x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
	}
	
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	// then, just before rendering
	//glBindFramebuffer(GL_FRAMEBUFFER, odsm_FBO);

	//setup matrices
	perspective_odsm = glm::perspective(90.0f, 1.0f, near_far.x, near_far.y);
	//pos x
	cm_rots[0] = glm::mat4(glm::vec4(0.0f, 0.0f, -1.0f, 0.0f),
							glm::vec4(0.0f, -1.0f, 0.0f, 0.0f),
							glm::vec4(-1.0f, 0.0f, 0.0f, 0.0f),
							glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
	//neg x
	cm_rots[1] = glm::mat4(glm::vec4(0.0, 0.0, 1.0, 0.0),
							glm::vec4(0.0, -1.0, 0.0, 0.0),
							glm::vec4(1.0, 0.0, 0.0, 0.0),
							glm::vec4(0.0, 0.0, 0.0, 1.0));
	//pos y
	cm_rots[2] = glm::mat4(glm::vec4(1.0, 0.0, 0.0, 0.0),
							glm::vec4(0.0, 0.0, -1.0, 0.0),
							glm::vec4(0.0, 1.0, 0.0, 0.0),
							glm::vec4(0.0, 0.0, 0.0, 1.0));
	//neg y
	cm_rots[3] = glm::mat4(glm::vec4(1.0, 0.0, 0.0, 0.0),
							glm::vec4(0.0, 0.0, 1.0, 0.0),
							glm::vec4(0.0, -1.0, 0.0, 0.0),
							glm::vec4(0.0, 0.0, 0.0, 1.0));
	//pos z
	cm_rots[4] = glm::mat4(glm::vec4(1.0, 0.0, 0.0, 0.0),
							glm::vec4(0.0, -1.0, 0.0, 0.0),
							glm::vec4(0.0, 0.0, -1.0, 0.0),
							glm::vec4(0.0, 0.0, 0.0, 1.0));
	//neg z
	cm_rots[5] = glm::mat4(glm::vec4(-1.0, 0.0, 0.0, 0.0),
							glm::vec4(0.0, -1.0, 0.0, 0.0),
							glm::vec4(0.0, 0.0, 1.0, 0.0),
							glm::vec4(0.0, 0.0, 0.0, 1.0));

	cm_trans = glm::translate(-light_0);

	//shadow_r2cm->bind();
	//char loc[10];
	//for (int i =0; i< 6; i++) {
	//	sprintf(loc, "cm_mat[%d]",i);
	//	GLint cm_mat_location = shadow_r2cm->get_uniform_location(loc);
	//	glUniformMatrix4fv( cm_mat_location, 1, GL_FALSE, glm::value_ptr(perspective_odsm * cm_rots[i] * cm_trans));
	//	
	//}
	//GLint nf_location = shadow_r2cm->get_uniform_location("near_far");
	//glUniform2f(nf_location, near_far.x, near_far.y);

	//GLint light_location = shadow_r2cm->get_uniform_location("light_pos");
	//glUniform4f(light_location, light_0.x, light_0.y, light_0.z, 1.0f);

	//shadow_r2cm->unbind();


	//view_odsm[i] = &(cm_rots[i] * cm_trans)[0][0];
}

//build an offset texture for soft shadows
void demo_shadow::init_offsets() 
{
	offset_size = 8;

	srand(time(0));
	//create the values, 3 per offset
	std::vector<GLfloat> offsets;
	for (int x = 0; x < offset_size; x++) {
		for (int y = 0; y < offset_size; y++) {
			//stratified offsets
			GLfloat r = (static_cast<GLfloat>(x) / static_cast<GLfloat>(offset_size)) * 2.0 - 1.0;
			GLfloat s = (static_cast<GLfloat>(y) / static_cast<GLfloat>(offset_size)) * 2.0 - 1.0;
			GLfloat t = 0;

			//add the offset vector
			offsets.push_back(r);
			offsets.push_back(s);
			offsets.push_back(t);
			offsets.push_back(0);
		}
	}

	//generate texture
	glGenTextures(1, &offset_tex);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, offset_tex);
	// Generate The Texture
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, offset_size, offset_size, 0, GL_RGBA, GL_FLOAT, &offsets[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);	// needs GL_NEAREST for particle storage to work properly!
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);	// needs GL_NEAREST for particle storage to work properly!
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 

	texture_odsm->set_uniform("offset_map", 2);
	glActiveTexture(GL_TEXTURE0);
}

void demo_shadow::draw()
{	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	load_identity(modelview_matrix);
	load_identity(lightview_matrix);

	//-----------------------
	//generate the shadow map
	//-----------------------
	//look at from light position
	//glEnable(GL_POLYGON_OFFSET_FILL);
	//glPolygonOffset(poly_offset_factor, poly_offset_units);
	//look_at(modelview_matrix, light_0, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

	//glBindFramebuffer(GL_FRAMEBUFFER, shadow_FBO);
	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//render_shadowmap(model);

	//--------------------------
	//generate cubemap shadowmap
	//--------------------------
	glBindFramebuffer(GL_FRAMEBUFFER, odsm_FBO);
	glViewport(0,0,cubemap_dim, cubemap_dim);
	
	//we do no not set any camera position via look_at
	//this way, modelview_matrix == model_matrix
	load_identity(modelview_matrix);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	render_odsm(object_root);

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	
	//glDisable(GL_POLYGON_OFFSET_FILL);
	

	//--------------
	//draw the scene
	//--------------
	
	glViewport(0, 0, width, height);
	//set camera position
	load_identity(view_matrix);
	load_identity(modelview_matrix);
	//we split view and model matrix:
	//modelview_matrix is the model transformation only
	//camera transformation is stored in view_matrix
	look_at(view_matrix, cam_eye, cam_center, cam_up);	
	//look_at(lightview_matrix, light_0, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
	//position light
	glm::vec4 temp =  view_matrix * glm::vec4(light_0, 0.0f);
	transformed_light_0 = glm::vec3(temp.x, temp.y, temp.z);
	glEnable(GL_FRAMEBUFFER_SRGB);

	render_node(root);

	glDisable(GL_FRAMEBUFFER_SRGB);
	
	glUseProgram(0);
}

//recursively render the scenegraph starting at node
void demo_shadow::render_node(scene_node& node)
{
	//save current transformation
	push_modelview_matrix();

	//apply transformation of this node
	modelview_matrix = modelview_matrix * node.transformation_matrix;

	//setup shader and render node
	//Shader* current_shader; //point to the shader of the node
	//GLint modelview_location,
	//	model_location,
	//	perspective_location, light_location, tex_location, shadowmap_location, lightview_location,
	//	os_location,
	//	near_far_location;
	//glm::mat4 range_map = glm::mat4(1.0f);
	//glm::mat4 modelview_inverted = glm::mat4(1.0f);
	//look_at(modelview_inverted, cam_eye, cam_center, cam_up);	
	/*modelview_inverted = glm::inverse(modelview_inverted);*/

	switch (node.shader_type())
	{
	case SH_NONE:
		break;
	case SH_TEXTURE:
		// Set our matrix uniform
		texture_odsm->bind();

		//do preparations before rendering
		node.pre_render();

		//set light
		texture_odsm->set_uniform("light_position", glm::vec3(transformed_light_0.x, transformed_light_0.y, transformed_light_0.z));

		texture_odsm->set_uniform("worldspace_light_position", glm::vec4(light_0.x, light_0.y, light_0.z, 0.0f));

		texture_odsm->set_uniform("near_far", near_far);
		
		texture_odsm->set_uniform("in_texture", 0);
		
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_CUBE_MAP, odsm_tex_ID);
		
		texture_odsm->set_uniform("shadow_cubemap", 1);

		
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_2D, offset_tex);
		texture_odsm->set_uniform("offset_map", 2);

		glActiveTexture(GL_TEXTURE0);


		texture_odsm->set_uniform("modelview_matrix", view_matrix * modelview_matrix);
		texture_odsm->set_uniform("model_matrix", modelview_matrix);
		texture_odsm->set_uniform("perspective_matrix", perspective_matrix);

		break;
	
	//regular shadowmap version
	//case SH_TEXTURE:
	//	// Set our matrix uniform
	//	texture_shader->bind();

	//	//do preparations before rendering
	//	node.pre_render();

	//	//set light
	//	light_location = texture_shader->get_uniform_location("light_position");
	//	glUniform3f(light_location, transformed_light_0.x, transformed_light_0.y, transformed_light_0.z);
	//	
	//	tex_location = texture_shader->get_uniform_location("in_texture");
	//	glUniform1i(tex_location, 0);
	//	
	//	glActiveTexture(GL_TEXTURE1);
	//	glBindTexture(GL_TEXTURE_2D, shadow_tex_ID);
	//	shadowmap_location = texture_shader->get_uniform_location("in_shadowmap");
	//	glUniform1i(shadowmap_location, 1);
	//	glActiveTexture(GL_TEXTURE0);

	//	modelview_location = texture_shader->get_uniform_location("modelview_matrix");
	//	glUniformMatrix4fv( modelview_location, 1, GL_FALSE, glm::value_ptr(modelview_matrix));

	//	// Set our matrix uniform
	//	perspective_location = texture_shader->get_uniform_location("perspective_matrix");
	//	glUniformMatrix4fv( perspective_location, 1, GL_FALSE, glm::value_ptr(perspective_matrix));

	//	//set lightview matrix
	//	//push_modelview_matrix();
	//	load_identity(lightview_matrix);
	//	look_at(lightview_matrix, light_0, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

	//	lightview_location = texture_shader->get_uniform_location("lightview_matrix");
	//	//bias to scale shadowmap values correctly
	//	range_map = glm::translate(0.5f, 0.5f, 0.5f) * glm::scale(0.5f, 0.5f, 0.5f);

	//	//complete lightview aka texture matrix
	//	//bias * Perspective_light * Cam_light * Cam_inverse *	modelview
	//	lightview_matrix = range_map * perspective_matrix * lightview_matrix *
	//						modelview_inverted * modelview_matrix;

	//	glUniformMatrix4fv( lightview_location, 1, GL_FALSE, glm::value_ptr(lightview_matrix));

		//pop_modelview_matrix();

		//break;
	default:
		break;

	}

	node.render();

	//render children
	std::vector<scene_node*>::iterator it;
	for (it = node.children.begin(); it != node.children.end(); it++)
	{
		render_node(**it);
	}

	//restore previous transformation
	pop_modelview_matrix();
}

 //render omni-directional shadow map
void demo_shadow::render_odsm(scene_node& node)
{	//save current transformation
	push_modelview_matrix();

	//apply transformation of this node
	modelview_matrix *= node.transformation_matrix;

	//setup shader and render node
	//Shader* current_shader; //point to the shader of the node
	GLint modelview_location, perspective_location, light_location, tex_location;
	switch (node.shader_type())
	{
	case SH_NONE:
		break;
	default:

		shadow_r2cm->bind();

		//do preparations before rendering
		//node.pre_render();

		shadow_r2cm->set_uniform("model_matrix", modelview_matrix);

		for (int i =0; i< 6; i++) {
			char mat_loc[10];
			sprintf(mat_loc, "cm_mat[%d]",i);
			shadow_r2cm->set_uniform(mat_loc, perspective_odsm * cm_rots[i] * glm::translate(-light_0) );
		}

		shadow_r2cm->set_uniform("near_far", near_far);

		shadow_r2cm->set_uniform("light_pos", glm::vec4(light_0.x, light_0.y, light_0.z, 1.0f));
		break;
	}

	node.render();

	glUseProgram(0);

	//render children
	std::vector<scene_node*>::iterator it;
	for (it = node.children.begin(); it != node.children.end(); it++)
	{
		render_odsm(**it);
	}

	//restore previous transformation
	pop_modelview_matrix();
}

//recursively render the scenegraph starting at node
void demo_shadow::render_shadowmap(scene_node& node)
{
	//save current transformation
	push_modelview_matrix();

	//apply transformation of this node
	modelview_matrix *= node.transformation_matrix;

	//setup shader and render node
	//Shader* current_shader; //point to the shader of the node
	GLint modelview_location, perspective_location, light_location, tex_location;
	switch (node.shader_type())
	{
	case SH_NONE:
		break;
	//case SH_TEXTURE:

		//set light
		//light_location = texture_shader->get_uniform_location("light_position");
		//glUniform3f(light_location, transformed_light_0.x, transformed_light_0.y, transformed_light_0.z);
		//
		//tex_location = texture_shader->get_uniform_location("in_texture");
		//glUniform1i(tex_location, 0);
		//break;
	default:
		//// Set our matrix uniform
		shadowmap_gen->bind();

		//do preparations before rendering
		node.pre_render();

		modelview_location = shadowmap_gen->get_uniform_location("modelview_matrix");
		glUniformMatrix4fv( modelview_location, 1, GL_FALSE, glm::value_ptr(modelview_matrix));

		// Set our matrix uniform
		perspective_location = shadowmap_gen->get_uniform_location("perspective_matrix");
		glUniformMatrix4fv( perspective_location, 1, GL_FALSE, glm::value_ptr(perspective_matrix));
		break;

	}

	node.render();

	glUseProgram(0);

	//render children
	std::vector<scene_node*>::iterator it;
	for (it = node.children.begin(); it != node.children.end(); it++)
	{
		render_shadowmap(**it);
	}

	//restore previous transformation
	pop_modelview_matrix();
}

void demo_shadow::cleanup()
{
	glDeleteVertexArrays(1, &bg_vao_id);

	glDeleteBuffers(4, bg_vbo_id);

	bg_vao_id = 0;
	for(int i=0; i<4; i++)
	{
		bg_vbo_id[i] = 0;
	}
}

void demo_shadow::process_keys()
{

	//factor determines the speed of the camera movement
	float factor = static_cast<float>(delta()) * 5;

	update_camera_movement(factor);
	update_light(factor);


}

void demo_shadow::update_light(float factor)
{
	float weight = 0.33;
	if (glfwGetKey(GLFW_KEY_RIGHT))
	{
		light_0.x += factor;
		//poly_offset_factor += factor * weight;
	}	
	if (glfwGetKey(GLFW_KEY_LEFT))
	{
		light_0.x -= factor;
		//poly_offset_factor -= factor * weight;
	}
	if (glfwGetKey(GLFW_KEY_UP))
	{
		light_0.z -= factor;
		//poly_offset_units += factor * weight;
	}	
	if (glfwGetKey(GLFW_KEY_DOWN))
	{
		light_0.z += factor;
		//poly_offset_units -= factor * weight;
	}
	if (glfwGetKey(GLFW_KEY_PAGEDOWN ))
	{
		light_0.y -= factor;
		//poly_offset_units += factor * weight;
	}	
	if (glfwGetKey(GLFW_KEY_PAGEUP))
	{
		light_0.y += factor;
		//poly_offset_units -= factor * weight;
	}
	light.transformation_matrix = glm::translate(light_0) * glm::scale(0.2f, 0.2f, 0.2f);
}

//init background quad
void demo_shadow::init_bg(const Shader &shader, GLuint *vbo_id, GLuint *vao_id)
{
		// Our triangle data
	GLfloat bg_positions[] = {	 
		-1.0f, -1.0f, 0.0f, 1.0f,
		1.0f, -1.0f, 0.0f, 1.0f,
		1.0f, 1.0f, 0.0f, 1.0f,
		-1.0f, 1.0f, 0.0f, 1.0f	};

	GLfloat bg_normals[] = {	
		0, 0, 1,
		0, 0, 1,
		0, 0, 1,
		0, 0, 1	};

	GLfloat bg_texcoords[] = {	 
		0.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 1.0f,
		0.0f, 1.0f	};

	GLuint bg_indices[] = {
		0,1,2,3,0,2
	};


	// Allocate and assign a Vertex Array Object to our handle

	glGenVertexArrays(1, vao_id);

	// Bind our Vertex Array Object as the current used object

	glBindVertexArray(*vao_id);

	// Allocate and assign two Vertex Buffer Objects (VBOs) to our handle
	glGenBuffers(4, vbo_id);

	// Bind our first VBO as being the active buffer and storing vertex attributes (coordinates)	
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[0]);
	glBufferData(GL_ARRAY_BUFFER, 4 * 4 * sizeof(GLfloat), bg_positions, GL_STATIC_DRAW);

	GLint vertex_location = shader.get_attrib_location("in_position");
	std::cout << "vertex_location of bg: " << vertex_location << std::endl;
	glEnableVertexAttribArray(vertex_location);
	glVertexAttribPointer(	vertex_location, 4, GL_FLOAT, 
		GL_FALSE, 0, NULL);
	get_errors();

	// Repeat for second VBO storing vertex colors
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[1]);
	glBufferData(GL_ARRAY_BUFFER, 4 * 3 * sizeof(GLfloat), bg_normals, GL_STATIC_DRAW);

	GLint normal_location = shader.get_attrib_location("in_normal");
	std::cout << "normal_location of bg: " << normal_location << std::endl;
	glEnableVertexAttribArray(normal_location);
	glVertexAttribPointer(	normal_location, 3, GL_FLOAT, 
		GL_FALSE, 0, NULL);
	get_errors();

	//vbo for texcoords
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[2]);
	glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(GLfloat), bg_texcoords, GL_STATIC_DRAW);
	
	GLint texcoord_location = shader.get_attrib_location("in_texcoord");
	glEnableVertexAttribArray(texcoord_location);
	glVertexAttribPointer(	texcoord_location, 2, GL_FLOAT, GL_FALSE, 0, NULL);

	//vbo for texcoords
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_id[3]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(bg_indices), bg_indices, GL_STATIC_DRAW);

	//glBindTexture(GL_TEXTURE_2D, texture[0]);
	get_errors();

	// unbind VAO
	glBindVertexArray(0);

	// Unbind VBOs
	glBindBuffer(GL_ARRAY_BUFFER, 0);


}