/** Example 028 CubeMapping

Shows usage of cubemap textures and how to do some simple environment mapping.
Cubemap textures have images for all 6 directions of a cube in a single texture.
Environment is used to reflect the environment around an object onto the object.
Cubemaps only work with shader materials which are written to support cube mapping.

@author Michael Zeilfelder, based on EnvCubeMap example from irrSpintz engine.

Start with the usual includes.
*/

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"

using namespace irr;


/*
	A callback class for our cubemap shader.
	We need a shader material which maps the cubemap texture to
	the polygon vertices of objects.
*/
class CubeMapReflectionCallback : public video::IShaderConstantSetCallBack
{
public:
	CubeMapReflectionCallback(scene::ISceneManager* smgr, int styleUVW)
		: SceneMgr(smgr)
		, StyleUVW(styleUVW), Roughness(0.f)
		, styleUvwID(-1) , worldViewProjID(-1), worldID(-1), cameraPosID(-1)
	{}

	/*
		Setting the style to map vertex UV-coordinates to the cubemap textures.
		- Specular style is typically used for mirrors and highlight reflections.
		- Diffuse style is commonly used in image based lighting calculations and
		often in combination with a higher roughness. Think of it as the sum of all
		light which reaches a point on your object.
		- Using model vertices directly for UV's is just nice for testing sometimes.
		Maybe has more uses? Experiment around :-)
	*/
	void SetStyleUVW(int style)
	{
		StyleUVW = style;
	}

	int GetStyleUVW() const
	{
		return StyleUVW;
	}

	/*
     We could also call this sharpness as the rougher a material the less
	 sharp the reflections of a cubemap are (light for rough materials
	 spreads out more while smooth materials reflect it more like a mirror).
	 Roughness is calculated using the mipmaps of the cubemap texture.
	 Note that rendertarget cubemap textures won't have mipmaps, so unfortunately
	 it won't work for those.
	 Also currently only OpenGL is able to interpolate seamless over cubemap borders.
	 On Direct3D9 you will only smooth per side, but not over side-borders.
	*/
	void SetRoughness(float roughness)
	{
		Roughness = roughness;
	}

	float getRoughness() const
	{
		return Roughness;
	}

	/*
		Typical code which passes a few values from c++ to shader.
	*/
	virtual void OnSetMaterial(const video::SMaterial& material)
	{}

	virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
	{
		video::IVideoDriver* driver = services->getVideoDriver();

		if ( worldViewProjID < 0 )	// first update
		{
			styleUvwID = services->getVertexShaderConstantID("StyleUVW");
			if( driver->getDriverType() == video::EDT_DIRECT3D9 )
			{
				worldViewProjID = services->getVertexShaderConstantID("WorldViewProj");
			}
			worldID = services->getVertexShaderConstantID("World");
			cameraPosID = services->getVertexShaderConstantID("CameraPos");
			roughnessID = services->getPixelShaderConstantID("Roughness");
		}

		services->setVertexShaderConstant(styleUvwID, &StyleUVW, 1 );

		irr::core::matrix4 world = driver->getTransform(irr::video::ETS_WORLD);
		services->setVertexShaderConstant(worldID, world.pointer(), 16);

		if( driver->getDriverType() == video::EDT_DIRECT3D9 )
		{
			irr::core::matrix4 worldViewProj;
			worldViewProj = driver->getTransform(irr::video::ETS_PROJECTION);
			worldViewProj *= driver->getTransform(irr::video::ETS_VIEW);
			worldViewProj *= world;
			services->setVertexShaderConstant(worldViewProjID, worldViewProj.pointer(), 16);
		}

		core::vector3df cameraPos = SceneMgr->getActiveCamera()->getAbsolutePosition();
		services->setVertexShaderConstant(cameraPosID, &cameraPos.X, 3 );
		services->setPixelShaderConstant(roughnessID, &Roughness, 1 );
	}

private:
	scene::ISceneManager* SceneMgr;

	int StyleUVW;		// 0 = specular, 1=diffuse, 2 = use model vertex coordinates for uvw.
	float Roughness;	// cubemap 0 = specular ... highest value depends on number of mipmaps in the texture

	irr::s32 styleUvwID;
	irr::s32 worldViewProjID;
	irr::s32 worldID;
	irr::s32 cameraPosID;
	irr::s32 roughnessID;
};

/*
	To keep the example compact our event-receiver acts also like a main
	application class. So it handles user input, updates the dynamic parts of
	the UI and it keeps some 3d nodes around.
*/
class MyEventReceiver : public IEventReceiver
{
public:
	MyEventReceiver() : Driver(0), Shader(0)
		,BackgroundSkybox(0), BackgroundCube(0)
		, CubemapUpdates(2)
		, CurrentStyleUVW(0), CurrentRoughness(0)
		, NeedCubemapUpdate(true)
	{
		StyleNamesUVW.push_back( L"specular" );
		StyleNamesUVW.push_back( L"diffuse" );
		StyleNamesUVW.push_back( L"model coordinates" );
	}

	// Handle the key input
	virtual bool OnEvent(const SEvent& event)
	{
		if (event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown == false)
		{
			switch(event.KeyInput.Key )
			{
			case KEY_SPACE:
				// Switch between different texture mapping styles
				if ( Shader )
				{
					Shader->SetStyleUVW((Shader->GetStyleUVW()+1)%StyleNamesUVW.size());
					updateStyleUVW();
				}
				break;
			case KEY_KEY_B:
				// Switch between our 2 different backgrounds
				if ( BackgroundSkybox && BackgroundCube )
				{
					if ( BackgroundSkybox->isVisible() )
					{
						BackgroundSkybox->setVisible(false);
						BackgroundCube->setVisible(true);
					}
					else
					{
						BackgroundSkybox->setVisible(true);
						BackgroundCube->setVisible(false);
					}
					NeedCubemapUpdate = true;
				}
				break;
			case KEY_KEY_I:
				// Show/hide the info text nodes
				for (u32 i=0; i<InfoTextNodes.size(); ++i )
					InfoTextNodes[i]->setVisible(!InfoTextNodes[i]->isVisible());
				break;
			case KEY_KEY_S:
				// Enable/disable seamless smoothing of mipmaps over cube borders
				if ( Driver )
				{
					Driver->disableFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS, Driver->queryFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS) );
					updateSeamless();
				}
				break;
			case KEY_KEY_U:
				// Switch dynamic cubemap updates on/off.
				CubemapUpdates = (CubemapUpdates+1) % 3;
				updateCubemapUpdates();
				break;
			case KEY_PLUS:
			case KEY_ADD:
				// Make material rougher
				if ( Shader )
				{
					Shader->SetRoughness( Shader->getRoughness() + 0.5f );
					updateRoughness();
				}
				break;
			case KEY_MINUS:
			case KEY_SUBTRACT:
			{
				// Make material smoother
				if ( Shader )
				{
					float roughness = Shader->getRoughness() - 0.5f;
					if ( roughness >= 0.f )
					{
						Shader->SetRoughness(roughness);
						updateRoughness();
					}
				}
				break;
			}
			default:
				break;
			}
		}

		return false;
	}

	// Some helper functions to update the UI
	void updateStyleUVW()
	{
		if ( CurrentStyleUVW && Shader)
			CurrentStyleUVW->setText(StyleNamesUVW[Shader->GetStyleUVW()].c_str());
	}

	void updateRoughness()
	{
		if ( CurrentRoughness && Shader )
		{
			CurrentRoughness->setText( irr::core::stringw(Shader->getRoughness()).c_str() );
		}
	}

	void updateSeamless()
	{
		if ( CurrentSeamlessCubemap && Driver )
		{
			CurrentSeamlessCubemap->setText( Driver->queryFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS) ? L"ON" : L"OFF" );
		}
	}

	void updateCubemapUpdates()
	{
		if ( CurrentCubemapUpdates )
		{
			switch ( CubemapUpdates )
			{
				case 0:	CurrentCubemapUpdates->setText( L"static");	break;
				case 1:	CurrentCubemapUpdates->setText( L"dynamic" );	break;
				case 2:	CurrentCubemapUpdates->setText( L"dynamic+mips" );	break;
			}
		}
	}

	// Check if the cubemap textures should be updated with new screenshots
	// return 0 for no update, 1 for update, 2 for update and fix mip-maps
	int checkCubemapUpdate()
	{
		if ( NeedCubemapUpdate || CubemapUpdates == 2)
		{
			NeedCubemapUpdate = false;
			return 2;
		}

		return CubemapUpdates;
	}

	// Add some text-node floating above it's parent node.
	void addInfoTextNode(irr::gui::IGUIFont* font, const wchar_t* text, irr::scene::ISceneNode* parent)
	{
		if ( parent )
		{
			const video::SColor infoTextCol(250, 70, 90, 90);
			core::dimension2du dim(font->getDimension(text));
			core::dimension2df dimf((f32)dim.Width, (f32)dim.Height);
			scene::IBillboardTextSceneNode* infoNode = parent->getSceneManager()->addBillboardTextSceneNode( font, text, parent, dimf, core::vector3df(0, 120, 0), -1, infoTextCol, infoTextCol);
			InfoTextNodes.push_back(infoNode);
		}
	}

	irr::video::IVideoDriver* Driver;
	CubeMapReflectionCallback* Shader;

	scene::ISceneNode* BackgroundSkybox;
	scene::ISceneNode* BackgroundCube;
	irr::core::array<scene::ISceneNode*> InfoTextNodes;

	int CubemapUpdates;	// 0 = static, 1 = dynamic, 2 = dynamic with rtt

	irr::core::array<irr::core::stringw> StyleNamesUVW;

	irr::gui::IGUIStaticText* CurrentStyleUVW;
	irr::gui::IGUIStaticText* CurrentRoughness;
	irr::gui::IGUIStaticText* CurrentSeamlessCubemap;
	irr::gui::IGUIStaticText* CurrentCubemapUpdates;

private:
	bool NeedCubemapUpdate;
};

/* Workaround for OpenGL's upside-down images.
 Texture origins (0,0) in OpenGL are usually at the left-bottom instead of the more common left-top image formats.
 Irrlicht internally uses textures with left-top origin and then corrects the texture-matrices in the fixed-function pipeline.
 For shader materials it's left to the users to handle those UV-flips for the texture-matrix.
 Render target textures (RTT's) in OpenGL are rendered with left-bottom origin and Irrlicht can't change that, so all RTT textures
 in memory are upside-down (unlike all other Irrlicht textures).
 In the fixed function pipeline Irrlicht handles this by flipping the RTT's texture matrix once more and for shaders it's again
 left to the users to handle it.
 Cubemap textures are different from other textures in OpenGL. Each cube side has left-top as the origin. So not flipping Irrlicht textures for those would be fine.
 Except - OpenGL RTT's still render left-bottom - even when the target is a cubemap RTT.
 I found no good way around this so far - it just seems messed up as we get a left-handed/right handed coordinate system change that way.

 So... the following 2 defines are two different workarounds I found. Both are ugly, which one is better in reality depends probably on the scene.
 Only use one of those:
	CUBEMAP_UPSIDE_DOWN_GL_PROJECTION is relatively fast as it just changes the project matrix. The problem is that changing the projection matrix
	means changing front/backside culling. So every node rendered has to flip the material flags for those.

	CUBEMAP_USPIDE_DOWN_RTT will change the texture memory itself and flip the image upside-down.
	While easier to do, this involves texture-locking and is very slow.
*/
#define CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
//#define CUBEMAP_USPIDE_DOWN_RTT


// Flip frontface/backface culling for all nodes
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
void flipCullingFlags(const core::array<scene::ISceneNode*>& nodes)
{
	for ( irr::u32 n=0; n < nodes.size(); ++n )
	{
		scene::ISceneNode* node = nodes[n];
		const irr::u32 matCount = node->getMaterialCount();
		for ( irr::u32 m=0; m < matCount; ++m)
		{
			video::SMaterial& mat = node->getMaterial(m);
			mat.BackfaceCulling = !mat.BackfaceCulling;
			mat.FrontfaceCulling = !mat.FrontfaceCulling;
		}
	}
}
#endif


/*
	Render the environment around a node into a cubemap texture.
*/
void renderEnvironmentCubeMap(irr::video::IVideoDriver* driver, irr::scene::ICameraSceneNode* cubeMapCamera, irr::scene::ISceneNode* cubeCenterNode, video::IRenderTarget* cubeMapRT, video::ITexture* dynamicCubeMapRTT, video::ITexture* depthStencilRTT)
{
	//	Change to the cubemap camera which has a few specific render-settings
	scene::ISceneManager* smgr = cubeMapCamera->getSceneManager();
	scene::ICameraSceneNode * oldCam = smgr->getActiveCamera();
	smgr->setActiveCamera( cubeMapCamera );

	/*
		We want to see everything around the center node, so hide the node
		itself, otherwise it would be in the way.
		Then set the camera to that node's position.
	*/
	cubeCenterNode->setVisible( false );
	const core::vector3df center( cubeCenterNode->getAbsolutePosition() );
	cubeMapCamera->setPosition( center );

	/*
		Render all 6 directions. Which means simple setting the camera target/up
		vector to all 6 directions and then render the full scene each time.
		So yeah - updating an environment cube-map means 6 full renders for each
		object which needs an environment map. In other words - you generally only
		want to do that in pre-processing, not in realtime.
	*/
	const core::vector3df targetVecs[6] = {
		core::vector3df(1.f, 0.f, 0.f),
		core::vector3df(-1.f, 0.f, 0.f),
		core::vector3df(0.f, 1.f, 0.f),
		core::vector3df(0.f, -1.f, 0.f),
		core::vector3df(0.f, 0.f, 1.f),
		core::vector3df(0.f, 0.f, -1.f)
	};

	const core::vector3df upVecs[6] = {
		core::vector3df( 0,1,0 ),
		core::vector3df( 0,1,0 ),
		core::vector3df( 0,0,-1 ),
		core::vector3df( 0,0,1 ),
		core::vector3df( 0,1,0 ),
		core::vector3df( 0,1,0 )
	};
	for ( int s=0; s<6; ++s )
	{
		cubeMapCamera->setUpVector( upVecs[s] );
		cubeMapCamera->setTarget( center + targetVecs[s] );
		// Here we tell into which side of the cubemap texture we want to write
		cubeMapRT->setTexture(dynamicCubeMapRTT, depthStencilRTT, (video::E_CUBE_SURFACE)(video::ECS_POSX + s));
		driver->setRenderTargetEx(cubeMapRT, video::ECBF_ALL);
		smgr->drawAll();

#ifdef CUBEMAP_USPIDE_DOWN_RTT
		// This works because the lock for rtt's always flips in Irrlicht.
		// So in this case lock() unlock will result in a flipped texture
		// But be warned - it's very, very slow!
		driver->setRenderTarget(0);	// to avoid accessing active rt
		dynamicCubeMapRTT->lock(video::ETLM_READ_WRITE, 0, s, video::ETLF_FLIP_Y_UP_RTT);
		dynamicCubeMapRTT->unlock();
#endif
	}

	//dynamicCubeMapRTT->regenerateMipMapLevels();	// Unfortunately we can't seem to have mipmaps for rtt's

	driver->setRenderTarget(0);
	cubeCenterNode->setVisible( true );
	smgr->setActiveCamera( oldCam );
}

/*
	Typical setup at the main start.
*/
int main()
{
	// Ask user for driver
	video::E_DRIVER_TYPE driverType = driverChoiceConsole();
	if (driverType==video::EDT_COUNT)
		return 1;

	// Create device
	MyEventReceiver eventReceiver;
	const core::dimension2d<u32> dimDevice(1024, 768);
	IrrlichtDevice* device = createDevice( driverType, dimDevice, 32, false, false, false, &eventReceiver );
	if (!device)
		return 1;

	const io::path mediaPath = getExampleMediaPath();
	video::IVideoDriver* driver = device->getVideoDriver();
	scene::ISceneManager* smgr = device->getSceneManager();
	gui::IGUIEnvironment* env = device->getGUIEnvironment();
	eventReceiver.Driver = driver;

	// Set window title
	core::stringw strCaption(L"Cubemap example - Irrlicht Engine [");
	strCaption += driver->getName();
	strCaption += L"]";
	device->setWindowCaption(strCaption.c_str());

	// set a nicer font
	gui::IGUISkin* skin = env->getSkin();
	gui::IGUIFont* font = env->getFont(mediaPath + "fonthaettenschweiler.bmp");
	if (font)
		skin->setFont(font);

	/*
		Create a shader material for cube mapping
	*/
	video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
	s32 cubeMapReflectionMaterial = 0;
	if( gpu )
	{
		// Decide on shader to use based on active driver
		irr::io::path vsFileName;
		irr::io::path psFileName;
		switch( driverType )
		{
			case video::EDT_DIRECT3D9:
				vsFileName = mediaPath + "cubeMapReflectionVS.hlsl";
				psFileName = mediaPath + "cubeMapReflectionPS.hlsl";
				break;

			case video::EDT_OPENGL:
				vsFileName = mediaPath + "cubeMapReflection.vert";
				psFileName = mediaPath + "cubeMapReflection.frag";
				break;
		}

		CubeMapReflectionCallback* cubeMapCB = new CubeMapReflectionCallback(smgr, 2);
		cubeMapReflectionMaterial = gpu->addHighLevelShaderMaterialFromFiles(
			vsFileName, "VS", video::EVST_VS_1_1,
			psFileName, "PS", video::EPST_PS_3_0,
			cubeMapCB, video::EMT_SOLID );
		if ( cubeMapReflectionMaterial >= 0 )
			eventReceiver.Shader = cubeMapCB;
		cubeMapCB->drop();
	}

	// add fps camera
	scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS(0, 100.f, 1.f);
	camera->setPosition( core::vector3df( 0,10,-200 ) );
	device->getCursorControl()->setVisible(false);

	/*
		Get 6 images forming a cubemap. The coordinate system used in those images
		seemed to be different than the one in Irrlicht. I decided to leave it like
		that because it's pretty common that way. If you get cubemap textures which
		seem to have x/y/z axis named different you'll just have to experiment until
		you figured out the correct order.
	*/
	core::array<video::IImage*> cubeMapImages;
	cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posx.jpg" ));
	cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negx.jpg" ));
	cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posy.jpg" ));
	cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negy.jpg" ));
	cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posz.jpg" ));
	cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negz.jpg" ));

	/* Create a cubemap texture from those images. Note that 6 images become a single texture now. */
	video::ITexture* cubeMapStaticTex = 0;
	cubeMapStaticTex = driver->addTextureCubemap("cm", cubeMapImages[0], cubeMapImages[1], cubeMapImages[2], cubeMapImages[3], cubeMapImages[4], cubeMapImages[5]);
	for ( u32 i=0; i<cubeMapImages.size(); ++i )
		if ( cubeMapImages[i] )
			cubeMapImages[i]->drop();
	cubeMapImages.clear();

	/* Create a render target, cubemap render-target-textures and a camera with settings for cube mapping */
	video::IRenderTarget* cubeMapRT = driver->addRenderTarget();
	video::ITexture* dynamicCubeMapRTT = 0;
	video::ITexture* depthStencilRTT = 0;
	video::ITexture* dynamicCubeMapRTT_intermediate = 0;	// just for rendering, but not used in material
	video::ITexture* dynamicCubeMapTex = 0;					// dynamic and with mipmaps
	scene::ICameraSceneNode* cubeMapCamera = 0;
	if( driver->queryFeature( video::EVDF_RENDER_TO_TARGET ) )
	{
		// Create cube map textures and render target cubemap textures.
		const u32 dynamicCubeMapSize = 512;
		dynamicCubeMapRTT = driver->addRenderTargetTextureCubemap(dynamicCubeMapSize, "cube_rtr");
		depthStencilRTT = driver->addRenderTargetTexture(irr::core::dimension2du(dynamicCubeMapSize, dynamicCubeMapSize), "cubemap_ds", irr::video::ECF_D24S8);

		dynamicCubeMapRTT_intermediate = driver->addRenderTargetTextureCubemap(dynamicCubeMapSize, "cube_rtr");
		dynamicCubeMapTex = driver->addTextureCubemap(dynamicCubeMapSize, "cube_tex");

		// Camera for creating an environment cubemap
		cubeMapCamera = smgr->addCameraSceneNode();
		cubeMapCamera->setFOV(core::PI* 0.5f);	// 90° view angle
		cubeMapCamera->setAspectRatio(1.f);	// it's a cube... all sides have the same length
		smgr->setActiveCamera( camera );
	}

	/*
		Add sphere-nodes which will be using the cubemaps as materials.
		You may also want to experiment with other node-types here!
	*/

	scene::ISceneNode* sphereNode = 0;
	scene::ISceneNode* sphereNode2 = 0;
	scene::ISceneNode* sphereNode3 = 0;
	scene::IMesh* sphereMesh = smgr->getGeometryCreator()->createSphereMesh(100.f);
	if( sphereMesh )
	{
		// Nothing really special here except they need the shader material to display cubemaps.
		sphereNode = smgr->addMeshSceneNode( sphereMesh );
		sphereNode->setPosition( core::vector3df(-250,0,0) );
		sphereNode->updateAbsolutePosition();
		sphereNode->setMaterialFlag( video::EMF_LIGHTING, false );
		sphereNode->setMaterialTexture( 0, dynamicCubeMapRTT );
		sphereNode->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
		eventReceiver.addInfoTextNode(font, L"Cubemap dynamic rtt, no mip-maps", sphereNode);

		if ( dynamicCubeMapTex )
		{
			sphereNode3 = smgr->addMeshSceneNode( sphereMesh );
			sphereNode3->setPosition( core::vector3df(0,0,250) );
			sphereNode3->updateAbsolutePosition();
			sphereNode3->setMaterialFlag( video::EMF_LIGHTING, false );
			sphereNode3->setMaterialTexture( 0, dynamicCubeMapTex );
			sphereNode3->getMaterial(0).TextureLayer[0].TrilinearFilter = false; // this is default anyway. It would be faster - but you can only access integer mip-levels - no filtering between mip-levels.
			sphereNode3->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
			eventReceiver.addInfoTextNode(font, L"Cubemap dynamic with mip-maps", sphereNode3);
		}

		if ( cubeMapStaticTex )
		{
			sphereNode2 = smgr->addMeshSceneNode( sphereMesh );
			sphereNode2->setPosition( core::vector3df(250,0,0) );
			sphereNode2->updateAbsolutePosition();
			sphereNode2->setMaterialFlag( video::EMF_LIGHTING, false );
			sphereNode2->setMaterialTexture( 0, cubeMapStaticTex );
			sphereNode2->getMaterial(0).TextureLayer[0].TrilinearFilter = true;		// this way smoothing happens between different mip-levels.
			sphereNode2->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
			eventReceiver.addInfoTextNode(font, L"Cubemap fixed images", sphereNode2);
		}

		sphereMesh->drop();
	}

	/* Add some background which will show up in the environment maps.
	   For first one we use the same textures as used in the spheres.
	   Note the difference between a skybox and a cubemap is that the skybox really uses 6 different
	   textures. While the cubemap uses a single texture created from 6 images. */
	eventReceiver.BackgroundSkybox = smgr->addSkyBoxSceneNode(
		driver->getTexture(mediaPath + "cubemap_posy.jpg"), // top
		driver->getTexture(mediaPath + "cubemap_negy.jpg"),	// bottom
		driver->getTexture(mediaPath + "cubemap_posz.jpg"),	// left
		driver->getTexture(mediaPath + "cubemap_negz.jpg"), // right
		driver->getTexture(mediaPath + "cubemap_posx.jpg"), // front
		driver->getTexture(mediaPath + "cubemap_negx.jpg")); // back



	/* Another background for comparison and to make it more obvious
	when the spheres reflect the environment and when they use static cubemaps. */
	scene::IMesh * cubeMesh = smgr->getGeometryCreator()->createCubeMesh( core::vector3df(10.f, 10.f, 10.f), scene::ECMT_6BUF_4VTX_NP);
	smgr->getMeshManipulator()->scale(cubeMesh, core::vector3df(-1, 1, 1));
	if( cubeMesh )
	{
		smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(0), video::SColor(255, 240, 10, 10) );
		smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(1), video::SColor(255, 240, 130, 10) );
		smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(2), video::SColor(255, 50, 250, 10)  );
		smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(3), video::SColor(255, 70, 10, 250) );
		smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(4), video::SColor(255, 240, 250, 10) );
		smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(5), video::SColor(255, 85, 250, 250)  );

		eventReceiver.BackgroundCube = smgr->addMeshSceneNode( cubeMesh );
		cubeMesh->drop();

		eventReceiver.BackgroundCube->setScale( core::vector3df( 200, 200, 200 ) );
		eventReceiver.BackgroundCube->setMaterialFlag( video::EMF_LIGHTING, false );
		eventReceiver.BackgroundCube->setVisible(false);
	}

#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
	if ( driverType == video::EDT_OPENGL )
	{
		// Flip projection matrix (note this also flips front/backface culling)
		core::matrix4 matProj = cubeMapCamera->getProjectionMatrix();
		matProj[4] = -matProj[4];
		matProj[5] = -matProj[5];
		matProj[6] = -matProj[6];
		matProj[7] = -matProj[7];
		cubeMapCamera->setProjectionMatrix(matProj);
	}
#endif

	/*
		Add some moving node to show the difference between static/dynamic environment maps
	*/
	scene::IMeshSceneNode * movingNode = smgr->addCubeSceneNode(30.f);
	movingNode->getMaterial(0).Lighting = false;
	smgr->getMeshManipulator()->setVertexColors( movingNode->getMesh()->getMeshBuffer(0), video::SColor(255, 230, 200, 150));
	scene::ISceneNodeAnimator* circleAnimator = smgr->createFlyCircleAnimator(core::vector3df(-125, -50.f, 125), 300.f, 0.0005f);
	movingNode->addAnimator(circleAnimator);
	circleAnimator->drop();

	/* Add some UI */
	if ( eventReceiver.Shader )
	{
		skin->setColor(gui::EGDC_3D_FACE, video::SColor(50, 160, 120, 120));

		u32 top = dimDevice.Height - 200;
		const u32 left = dimDevice.Width - 350;
		const u32 right = dimDevice.Width - 10;
		irr::gui::IGUIStaticText * stextUVW = env->addStaticText(L" Style of generating texture coordinates:\n Change with (space)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
		top += 40;
		stextUVW->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
		eventReceiver.CurrentStyleUVW = env->addStaticText(L"", core::recti(240,0, 400, 20), false, false, stextUVW);
		eventReceiver.updateStyleUVW();

		irr::gui::IGUIStaticText * stextRoughness = env->addStaticText(L" Roughness:\n Change with (+) and (-)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
		top += 40;
		eventReceiver.CurrentRoughness = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextRoughness);
		eventReceiver.updateRoughness();

		irr::gui::IGUIStaticText * stextSeamlessCupemap = env->addStaticText(L" Seamless cubemap (with roughness):\n Change with (s)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
		top += 40;
		eventReceiver.CurrentSeamlessCubemap = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextSeamlessCupemap);
		eventReceiver.updateSeamless();

		irr::gui::IGUIStaticText * stextUpdates = env->addStaticText(L" Cubemap updates:\n Change with (u)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
		top += 40;
		eventReceiver.CurrentCubemapUpdates = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextUpdates);
		eventReceiver.updateCubemapUpdates();

		env->addStaticText(L" Change background with (b)", core::recti(left, top, right, top+15), false, true, 0, -1, true);
		top += 20;

		env->addStaticText(L" Show/hide info nodes with (i)", core::recti(left, top, right, top+15), false, true, 0, -1, true);
	}


	/* Main loop */
	while(device->run())
	{
		if (device->isWindowActive())
		{
			driver->beginScene(true, true, video::SColor(255, 127, 127, 255));

			/* Check if we want to update the environment maps.
			   Usually not something you'll do every frame, but either once at the star
			   or maybe updating an environment map once in a while.
			*/
			int updateCubemaps = eventReceiver.checkCubemapUpdate();
			if( dynamicCubeMapRTT && sphereNode && updateCubemaps > 0 )
			{
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
				core::array<scene::ISceneNode*> allNodes;
				if ( driverType == video::EDT_OPENGL )
				{
					/*
						Flipping projection matrix flips front/backface culling.
						We only have a skybox so in this case this still would be fast, with more objects it's getting more ugly.
					*/
					smgr->getSceneNodesFromType(scene::ESNT_ANY, allNodes);
					flipCullingFlags(allNodes);
				}
#endif
				/*
					If rendered just once then this node has still a white (or even undefined) texture at this point
					Just hiding it and render the background when rendering the cubemap for the other node is less noticable
					than having a big white dot in the environment texture.
					Render order can matter if you want several environment maps in your scene.
				*/
				if (sphereNode3)
					sphereNode3->setVisible(false);

				renderEnvironmentCubeMap(driver, cubeMapCamera, sphereNode, cubeMapRT, dynamicCubeMapRTT, depthStencilRTT);

				if ( sphereNode3)
				{
					if ( updateCubemaps == 2 )
					{
						/*
							Our rtt's unfortunately don't have mipmaps (sorry, not sure if we can get that somehow...)
							So if we want mipmaps in the dynamic cubemap we have to copy it to a non-rtt texture.
							Warning: Very, very slow. Far slower than just creating an environment map as this
							will copy the texture from GPU to main memory - copy it to a new texture, create mip-maps and
							upload the result back to the GPU.
						 */
						renderEnvironmentCubeMap(driver, cubeMapCamera, sphereNode3, cubeMapRT, dynamicCubeMapRTT_intermediate, depthStencilRTT);
						for ( int i=0; i<6; ++i)
						{
							void * rtData = dynamicCubeMapRTT_intermediate->lock(video::ETLM_READ_ONLY, 0, i, video::ETLF_NONE);
							void * tData = dynamicCubeMapTex->lock(video::ETLM_READ_WRITE, 0, i);
							memcpy(tData, rtData, dynamicCubeMapTex->getPitch()*dynamicCubeMapTex->getSize().Width);
							dynamicCubeMapRTT_intermediate->unlock();
							dynamicCubeMapTex->unlock();
							dynamicCubeMapTex->regenerateMipMapLevels();
						}
					}
					sphereNode3->setVisible(true);
				}

#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
				if ( driverType == video::EDT_OPENGL )
				{
					flipCullingFlags(allNodes);
				}
#endif
			}

			smgr->drawAll();
			env->drawAll();

			driver->endScene();
		}
	}

	device->drop();

	return 0;
}

/*
**/
