/*-------------------------------------------------------------------------------------
Copyright (c) 2006 John Judnich
Modified 2008 by Erik Hjortsberg (erik.hjortsberg@iteam.se)

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
    1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
    2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
    3. This notice may not be removed or altered from any source distribution.
-------------------------------------------------------------------------------------*/

//ImpostorPage.cpp
//ImposterPage is an extension to PagedGeometry which displays entities as imposters.
//-------------------------------------------------------------------------------------

#include <OgreRoot.h>
#include <OgreTimer.h>
#include <OgreCamera.h>
#include <OgreVector3.h>
#include <OgreQuaternion.h>
#include <OgreEntity.h>
#include <OgreSubEntity.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreTechnique.h>
#include <OgrePass.h>

#include <OgreViewport.h>
#include <Compositor/OgreCompositorManager2.h>
#include <Compositor/OgreCompositorWorkspace.h>
#include <Compositor/OgreCompositorWorkspaceListener.h>
#include <Compositor/OgreCompositorWorkspaceDef.h>
#include <Compositor/OgreCompositorNodeDef.h>
#include <Compositor/Pass/PassClear/OgreCompositorPassClearDef.h>
#include <Compositor/Pass/PassScene/OgreCompositorPassSceneDef.h>
#include <Compositor/Pass/OgreCompositorPass.h>

#include "ImpostorPage.h"
#include "StaticBillboardSet.h"

using namespace Ogre;
using namespace Forests;

// static members initialization
Ogre::uint  Forests::ImpostorPage::s_nImpostorResolution    = 256;
Ogre::uint  Forests::ImpostorPage::s_nSelfInstances         = 0;
Ogre::uint  Forests::ImpostorPage::s_nUpdateInstanceID      = 0;
ColourValue Forests::ImpostorPage::s_clrImpostorBackground  = ColourValue(0.34f, 0.63f, 0.0f, 0.0f);
BillboardOrigin Forests::ImpostorPage::s_impostorPivot      = BBO_CENTER;

Ogre::SceneNode* Forests::ImpostorPage::m_RenderNode = 0;
Ogre::SceneNode* Forests::ImpostorPage::m_CameraNode = 0;

class ImpostorRenderListener : public Ogre::CompositorWorkspaceListener
{
protected:
    Ogre::SceneNode* mCameraNode;
    float              mObjDist;
    Ogre::Vector3       mEntityCenter;
public:
    ImpostorRenderListener(Ogre::SceneNode* cameraNode, float objDist, Ogre::Vector3 entityCenter)
        : Ogre::CompositorWorkspaceListener(), mCameraNode(cameraNode), mObjDist(objDist),mEntityCenter(entityCenter) {}

	virtual void workspacePreUpdate()
	{
		
	}

	virtual void  passPreExecute( Ogre::CompositorPass *pass )
	{

        if (pass->getType() == Ogre::PASS_SCENE)
        {
            const float xDivFactor = 1.0f / IMPOSTOR_YAW_ANGLES;
            const float yDivFactor = 1.0f / IMPOSTOR_PITCH_ANGLES;
            uint32 identifier = pass->getDefinition()->mIdentifier;

            int o, i;
            //a bit dirty
            o = identifier / 1000;
            i = identifier - o;

#ifdef IMPOSTOR_RENDER_ABOVE_ONLY
            Radian pitch = Degree((90.0f * o) * yDivFactor); //0, 22.5, 45, 67.5
#else
            Radian pitch = Degree((180.0f * o) * yDivFactor - 90.0f);
#endif

            Radian yaw = Degree((360.0f * i) * xDivFactor); //0, 45, 90, 135, 180, 225, 270, 315

            //Position camera
            mCameraNode->setPosition(mEntityCenter);
            mCameraNode->setOrientation(Quaternion(yaw, Vector3::UNIT_Y) * Quaternion(-pitch, Vector3::UNIT_X));
            mCameraNode->translate(Vector3(0, 0, mObjDist), Node::TS_LOCAL);
        }
	}
};
      

//-----------------------------------------------------------------------------
/// Default constructor
ImpostorPage::ImpostorPage() :
m_pSceneMgr    (NULL),
m_pPagedGeom   (NULL),
m_blendMode    (ALPHA_REJECT_IMPOSTOR),
m_nInstanceID  (0),
m_nAveCount    (0),
m_vecCenter    (0, 0, 0)
{
   ++s_nSelfInstances;
}

//-----------------------------------------------------------------------------
/// Destructor
ImpostorPage::~ImpostorPage()
{
   TImpostorBatchs::iterator iter = m_mapImpostorBatches.begin(), iend = m_mapImpostorBatches.end();
   while (iter != iend)
   {
      delete iter->second;
      ++iter;
   }

   if (--s_nSelfInstances == 0 && m_pPagedGeom)
   {
      if (m_pPagedGeom->getSceneNode())
      {
          m_pPagedGeom->getSceneNode()->removeAndDestroyChild(m_RenderNode);
		  m_pPagedGeom->getSceneNode()->removeAndDestroyChild(m_CameraNode);
      }
      else if (m_pSceneMgr)
      {
		  m_pSceneMgr->destroySceneNode(m_RenderNode);
		  m_pSceneMgr->destroySceneNode(m_CameraNode);
      }
      else
      {
         assert(false && "Who must delete scene node???");
      }
	  ResourceGroupManager::getSingleton().unloadResourceGroup("Impostors");
      ResourceGroupManager::getSingleton().destroyResourceGroup("Impostors");
   }
}

//-----------------------------------------------------------------------------
///
void ImpostorPage::init(PagedGeometry *geom, const Ogre::Any &data)
{
   assert(geom && "Null pointer to PagedGeometry");
	m_pSceneMgr    = geom->getSceneManager();
	m_pPagedGeom   = geom;

	if (s_nSelfInstances == 1)  // first instance
   {
		// Set up a single instance of a scene node which will be used when rendering impostor textures
		m_RenderNode = geom->getSceneNode()->createChildSceneNode(Ogre::SCENE_STATIC);
		m_CameraNode = geom->getSceneNode()->createChildSceneNode(Ogre::SCENE_DYNAMIC);
      ResourceGroupManager::getSingleton().createResourceGroup("Impostors");
	}
}

//-----------------------------------------------------------------------------
///
void ImpostorPage::setRegion(Ogre::Real left, Ogre::Real top, Ogre::Real right, Ogre::Real bottom)
{
	// Calculate center of region
	m_vecCenter.x  = (left + right) * 0.5f;
	m_vecCenter.z  = (top + bottom) * 0.5f;
	m_vecCenter.y  = 0.0f; // The center.y value is calculated when the entities are added
	m_nAveCount    = 0;
}

//-----------------------------------------------------------------------------
///
void ImpostorPage::addEntity(Entity *ent, const Vector3 &position, const Quaternion &rotation, const Vector3 &scale, const Ogre::ColourValue &color)
{
	//Get the impostor batch that this impostor will be added to
	ImpostorBatch *ibatch = ImpostorBatch::getBatch(this, ent);

	//Then add the impostor to the batch
	ibatch->addBillboard(position, rotation, scale, color);

	//Add the Y position to the center.y value (to be averaged later)
	m_vecCenter.y += position.y + ent->getMesh()->getBounds().getCenter().y * scale.y;
	++m_nAveCount;
}

//-----------------------------------------------------------------------------
///
void ImpostorPage::build()
{
   if (m_mapImpostorBatches.empty())
      return;

	// Calculate the average Y value of all the added entities
   m_vecCenter.y = m_nAveCount > 0 ? m_vecCenter.y /= m_nAveCount : 0;

	//Build all batches
	TImpostorBatchs::iterator it = m_mapImpostorBatches.begin(), iend = m_mapImpostorBatches.end();
   while (it != iend)
   {
      it->second->build();
      ++it;
   }
}

//-----------------------------------------------------------------------------
///
void ImpostorPage::setVisible(bool visible)
{
	// Update visibility status of all batches
	TImpostorBatchs::iterator it = m_mapImpostorBatches.begin(), iend = m_mapImpostorBatches.end();
   while (it != iend)
   {
      it->second->setVisible(visible);
      ++it;
   }
}
//-----------------------------------------------------------------------------
void ImpostorPage::setVisibilityFlags(Ogre::uint32 visibilityFlags)
{
	//Update visibility flags of all batches
	TImpostorBatchs::iterator it = m_mapImpostorBatches.begin(), iend = m_mapImpostorBatches.end();
   while (it != iend)
   {
      it->second->setVisibilityFlags(visibilityFlags);
      ++it;
   }
}
//-----------------------------------------------------------------------------
void ImpostorPage::setCastShadows(bool castShadows)
{
	//Update visibility flags of all batches
	TImpostorBatchs::iterator it = m_mapImpostorBatches.begin(), iend = m_mapImpostorBatches.end();
   while (it != iend)
   {
      it->second->setCastShadows(castShadows);
      ++it;
   }
}
//-----------------------------------------------------------------------------
///
void ImpostorPage::setFade(bool enabled, Real visibleDist, Real invisibleDist)
{
	// Update fade status of all batches
	TImpostorBatchs::iterator it = m_mapImpostorBatches.begin(), iend = m_mapImpostorBatches.end();
   while (it != iend)
   {
      it->second->setFade(enabled, visibleDist, invisibleDist);
      ++it;
   }
}

//-----------------------------------------------------------------------------
///
void ImpostorPage::removeEntities()
{
	TImpostorBatchs::iterator iter = m_mapImpostorBatches.begin(), iend = m_mapImpostorBatches.end();
   while (iter != iend)
   {
      iter->second->clear();
      ++iter;
	}

	//Reset y center
	m_vecCenter.y  = 0;
	m_nAveCount    = 0;
}

//-----------------------------------------------------------------------------
///
void ImpostorPage::update()
{
   if (m_mapImpostorBatches.empty())  // SVA speed up
      return;

	//Calculate the direction the impostor batches should be facing

    Vector3 camPos = m_pPagedGeom->_convertToLocal(m_pPagedGeom->getCamera()->getDerivedPosition());
	// Update all batches

    Ogre::Real distX = camPos.x - m_vecCenter.x;
	Ogre::Real distZ = camPos.z - m_vecCenter.z;
	Ogre::Real distY = camPos.y - m_vecCenter.y;
	Ogre::Real distRelZ = Math::Sqrt(distX * distX + distZ * distZ);

	Ogre::Vector3 camDir(distX,distY,distZ);
	camDir.normalise();
	Radian pitch = Math::ACos(1-camDir.dotProduct(Ogre::Vector3::UNIT_Y));
	//Radian pitch = Math::ATan2(distY, distRelZ);

	Radian yaw;
	if (distRelZ > m_pPagedGeom->getPageSize() * 3)
   {
		yaw = Math::ATan2(distX, distZ);
	}
   else
   {
		Vector3 dir = m_pPagedGeom->_convertToLocal(m_pPagedGeom->getCamera()->getDerivedDirection());
		yaw = Math::ATan2(-dir.x, -dir.z);
	}

   TImpostorBatchs::iterator iter = m_mapImpostorBatches.begin(), iend = m_mapImpostorBatches.end();
   while (iter != iend)
   {
      iter->second->setAngle(pitch.valueDegrees(), yaw.valueDegrees());
      ++iter;
   }
}

void ImpostorPage::regenerate(Entity *ent)
{
	ImpostorTexture *tex = ImpostorTexture::getTexture(NULL, ent);
	if (tex != NULL)
		tex->regenerate();
}

void ImpostorPage::regenerateAll()
{
	ImpostorTexture::regenerateAll();
}

//-------------------------------------------------------------------------------------

unsigned long ImpostorBatch::GUID = 0;

ImpostorBatch::ImpostorBatch(ImpostorPage *group, Entity *entity) :
m_pTexture  (NULL)
{
	// Render impostor texture for this entity
	m_pTexture = ImpostorTexture::getTexture(group, entity);

	//Create billboard set
   PagedGeometry *pg = group->getParentPagedGeometry();
   bbset = new StaticBillboardSet(pg->getSceneManager(), pg->getSceneNode());
	bbset->setTextureStacksAndSlices(IMPOSTOR_PITCH_ANGLES, IMPOSTOR_YAW_ANGLES);

	setBillboardOrigin(ImpostorPage::getImpostorPivot());

	//Default the angle to 0 degrees
	pitchIndex = -1;
	yawIndex = -1;
	setAngle(0.0f, 0.0f);

	//Init. variables
	igroup = group;
}

ImpostorBatch::~ImpostorBatch()
{
	//Delete billboard set
	delete bbset;

	// Delete texture
	ImpostorTexture::removeTexture(m_pTexture);
}

//Returns a pointer to an ImpostorBatch for the specified entity in the specified
//ImpostorPage. If one does not already exist, one will automatically be created.
ImpostorBatch *ImpostorBatch::getBatch(ImpostorPage *group, Entity *entity)
{
	//Search for an existing impostor batch for this entity
	String entityKey = ImpostorBatch::generateEntityKey(entity);
   ImpostorBatch *batch = group->getImpostorBatch(entityKey);
   if (batch)  // If found return it
      return batch;

   // Otherwise, create a new batch
   batch = new ImpostorBatch(group, entity);
   group->injectImpostorBatch(entityKey, batch);   // warning! function can return false
   return batch;
}

//Rotates all the impostors to the specified angle (virtually - it actually changes
//their materials to produce this same effect)
void ImpostorBatch::setAngle(Ogre::Real pitchDeg, Ogre::Real yawDeg)
{
	// Calculate pitch material index
	int newPitchIndex = 0;
#ifdef IMPOSTOR_RENDER_ABOVE_ONLY
	if (pitchDeg > 0)
   {
		float maxPitchIndexDeg = (90.0f * (IMPOSTOR_PITCH_ANGLES-1)) / IMPOSTOR_PITCH_ANGLES;
		newPitchIndex = (int)(IMPOSTOR_PITCH_ANGLES * (pitchDeg / maxPitchIndexDeg));
		if (newPitchIndex > IMPOSTOR_PITCH_ANGLES-1) newPitchIndex = IMPOSTOR_PITCH_ANGLES-1;
	}
#else
	float minPitchIndexDeg = -90.0f;
	float maxPitchIndexDeg = ((180.0f * (IMPOSTOR_PITCH_ANGLES-1)) / IMPOSTOR_PITCH_ANGLES) - 90.0f;
	newPitchIndex = (int)(IMPOSTOR_PITCH_ANGLES * ((pitchDeg - minPitchIndexDeg) / (maxPitchIndexDeg - minPitchIndexDeg)));
	if (newPitchIndex > IMPOSTOR_PITCH_ANGLES-1) newPitchIndex = IMPOSTOR_PITCH_ANGLES-1;
	if (newPitchIndex < 0) newPitchIndex = 0;
#endif

	/*// Calculate yaw material index
   int newYawIndex = yawDeg > 0 ? int(IMPOSTOR_YAW_ANGLES * (yawDeg / 360.0f) + 0.5f) % IMPOSTOR_YAW_ANGLES :
      int(IMPOSTOR_YAW_ANGLES + IMPOSTOR_YAW_ANGLES * (yawDeg / 360.0f) + 0.5f) % IMPOSTOR_YAW_ANGLES;

	// Change materials if necessary
	if (newPitchIndex != pitchIndex || newYawIndex != yawIndex)
   {
		pitchIndex = newPitchIndex;
		yawIndex = newYawIndex;
		bbset->setMaterial(m_pTexture->material[pitchIndex][yawIndex]->getName());
	}*/
	bbset->setMaterial(m_pTexture->material->getName());
}

void ImpostorBatch::setBillboardOrigin(BillboardOrigin origin)
{
	bbset->setBillboardOrigin(origin);

	if (bbset->getBillboardOrigin() == BBO_CENTER)
		entityBBCenter = m_pTexture->entityCenter;
	else if (bbset->getBillboardOrigin() == BBO_BOTTOM_CENTER)
		entityBBCenter = Vector3(m_pTexture->entityCenter.x, m_pTexture->entityCenter.y - m_pTexture->entityRadius, m_pTexture->entityCenter.z);
}

String ImpostorBatch::generateEntityKey(Entity *entity)
{
	StringStream entityKey;
	entityKey << entity->getMesh()->getName();
	for (unsigned int i = 0; i < entity->getNumSubEntities(); ++i)
   {
		entityKey << "-" << entity->getSubEntity(i)->getMaterialName();
	}
	entityKey << "-" << IMPOSTOR_YAW_ANGLES << "_" << IMPOSTOR_PITCH_ANGLES;
#ifdef IMPOSTOR_RENDER_ABOVE_ONLY
	entityKey << "_RAO";
#endif
	return entityKey.str();
}

//-------------------------------------------------------------------------------------

ImpostorTextureResourceLoader::ImpostorTextureResourceLoader(ImpostorTexture& impostorTexture)
: texture(impostorTexture)
{
}

void ImpostorTextureResourceLoader::loadResource (Ogre::Resource *resource)
{
	if (resource->getLoadingState() == Ogre::Resource::LOADSTATE_UNLOADED) {
		texture.regenerate();
	}
}

//-------------------------------------------------------------------------------------

std::map<String, ImpostorTexture *> ImpostorTexture::selfList;
unsigned long ImpostorTexture::GUID = 0;
Ogre::String ImpostorTexture::mDefaultMaterialName = "none";
Ogre::String ImpostorTexture::mResourceGroupName = "";
bool		ImpostorTexture::mUseUserDefinedTexture = false;

//Do not use this constructor yourself - instead, call getTexture()
//to get/create an ImpostorTexture for an Entity.
ImpostorTexture::ImpostorTexture(ImpostorPage *group, Entity *entity) :
loader(0)
{
	//Store scene manager and entity
   ImpostorTexture::sceneMgr = group->getParentPagedGeometry()->getSceneManager();
	ImpostorTexture::entity = entity;
	ImpostorTexture::group = group;

	//Add self to list of ImpostorTexture's
	entityKey = ImpostorBatch::generateEntityKey(entity);
	typedef std::pair<String, ImpostorTexture *> ListItem;
	selfList.insert(ListItem(entityKey, this));

	//Calculate the entity's bounding box and it's diameter
	boundingBox = entity->getMesh()->getBounds();

	entityRadius = Math::boundingRadiusFromAABB(boundingBox);
	entityDiameter = 2.0f * entityRadius;
	entityCenter = boundingBox.getCenter();

	//Render impostor textures
	renderTextures(false);

	//Set up materials

	if(mDefaultMaterialName == "none")
	{
		material = MaterialManager::getSingleton().create(getUniqueID("ImpostorMaterial"), "Impostors");
	}
	else
	{
		MaterialPtr defaultMaterial = MaterialManager::getSingleton().getByName(mDefaultMaterialName);
		material = defaultMaterial->clone(getUniqueID("ImpostorMaterial"),true,"Impostors");
	}

	Material *m = material.getPointer();
	Pass *p = m->getTechnique(0)->getPass(0);

	TextureUnitState *t = p->createTextureUnitState(texture->getName());

	p->setLightingEnabled(false);
	m->setReceiveShadows(false);

	if (group->getBlendMode() == ALPHA_REJECT_IMPOSTOR){
		//p->setAlphaRejectSettings(CMPF_GREATER_EQUAL, 128);
		p->setAlphaRejectSettings(CMPF_GREATER_EQUAL, 64);
	} else if (group->getBlendMode() == ALPHA_BLEND_IMPOSTOR){
		p->setSceneBlending(SBF_SOURCE_ALPHA, SBF_ONE_MINUS_SOURCE_ALPHA);
		p->setDepthWriteEnabled(false);
	}

}
void ImpostorTexture::setDefaultMaterialName(const Ogre::String & materialName)
{
	mDefaultMaterialName=materialName;
}
void ImpostorTexture::setResourceGroupName(const Ogre::String & resourceGroup)
{
	mResourceGroupName=resourceGroup;
}
void ImpostorTexture::useUserDefinedTexture(bool use)
{
	mUseUserDefinedTexture=use;
}
void ImpostorTexture::updateMaterials()
{
	Material *m = material.getPointer();
	Pass *p = m->getTechnique(0)->getPass(0);

	TextureUnitState *t = p->getTextureUnitState(0);

	t->setTextureName(texture->getName());
}

ImpostorTexture::~ImpostorTexture()
{
	//Delete textures
	assert(!texture.isNull());
	String texName(texture->getName());

	texture.setNull();
	if (TextureManager::getSingletonPtr())
		TextureManager::getSingleton().remove(texName);

	//Delete materials

	assert (!material.isNull());
	String matName(material->getName());

	material.setNull();
	if (MaterialManager::getSingletonPtr())
		MaterialManager::getSingleton().remove(matName);


	//Remove self from list of ImpostorTexture's
	selfList.erase(entityKey);
}

void ImpostorTexture::regenerate()
{
	assert(!texture.isNull());
	String texName(texture->getName());
	texture.setNull();
	if (TextureManager::getSingletonPtr())
		TextureManager::getSingleton().remove(texName);

	renderTextures(true);
	updateMaterials();
}

void ImpostorTexture::regenerateAll()
{
	std::map<String, ImpostorTexture *>::iterator iter;
	for (iter = selfList.begin(); iter != selfList.end(); ++iter){
		iter->second->regenerate();
	}
}

void ImpostorTexture::renderTextures(bool force)
{
	//TODO: PORT TO OGRE 2.0
	
	if(mUseUserDefinedTexture)
	{
		String fileNameTGA = entity->getMesh()->getName()+".tga";
		String fileNameDDS = entity->getMesh()->getName()+".dds";
		if(Ogre::TextureManager::getSingleton().resourceExists(fileNameDDS))
		{
			texture = TextureManager::getSingleton().load(fileNameDDS, mResourceGroupName, TEX_TYPE_2D, MIP_UNLIMITED);
		}
		else
		{
			texture = TextureManager::getSingleton().load(fileNameTGA, mResourceGroupName, TEX_TYPE_2D, MIP_UNLIMITED);
		}
	}
	else
    {
#ifdef IMPOSTOR_FILE_SAVE
	TexturePtr renderTexture;
#else
	TexturePtr renderTexture(texture);
	//if we're not using a file image we need to set up a resource loader, so that the texture is regenerated if it's ever unloaded (such as switching between fullscreen and the desktop in win32)
	loader = std::auto_ptr<ImpostorTextureResourceLoader>(new ImpostorTextureResourceLoader(*this));
#endif
	RenderTexture *renderTarget;
	Camera *renderCamera;
	SceneNode *camNode;

	//Set up RTT texture
   Ogre::uint textureSize = ImpostorPage::getImpostorResolution()*2;
	if (renderTexture.isNull())
   {
	renderTexture = TextureManager::getSingleton().createManual(getUniqueID("ImpostorTexture"), "Impostors",
				TEX_TYPE_2D, textureSize * IMPOSTOR_YAW_ANGLES, textureSize * IMPOSTOR_PITCH_ANGLES, 0, PF_A8R8G8B8, TU_RENDERTARGET, loader.get());
	}
	renderTexture->setNumMipmaps(MIP_UNLIMITED);

	
	//Set up render target
	renderTarget = renderTexture->getBuffer()->getRenderTarget();
	//renderTarget->setActive(false);
	//renderTexture->setFSAA(8,"Quality");


	//Set up camera
    camNode = ImpostorTexture::group->getCameraNode();
	renderCamera = sceneMgr->createCamera(getUniqueID("ImpostorCam"));
    renderCamera->detachFromParent();
	camNode->attachObject(renderCamera);
	renderCamera->setLodBias(1000.0f);
    renderCamera->_resetRenderedRqs(96);

    const Ogre::IdString workspaceName("ImpostorPage::RenderImpostors");

    Ogre::CompositorManager2 *compositorManager = Ogre::Root::getSingleton().getCompositorManager2();
    Ogre::CompositorWorkspace* workSpace = 0;

    Ogre::CompositorWorkspaceDef *workspaceDef = 0;
    if (!compositorManager->hasWorkspaceDefinition(workspaceName))
    {
        
        Ogre::CompositorNodeDef* nodeDef = compositorManager->addNodeDefinition("ImpostorPage::RenderImpostors::Node");
        //Input texture
        nodeDef->addTextureSourceName(renderTarget->getName(), 0, Ogre::TextureDefinitionBase::TEXTURE_INPUT);
        nodeDef->setNumTargetPass(1);
        {
            Ogre::CompositorTargetDef *targetDef = nodeDef->addTargetPass(renderTarget->getName());

            unsigned int numPasses = IMPOSTOR_PITCH_ANGLES*IMPOSTOR_YAW_ANGLES +1;
            targetDef->setNumPasses(numPasses);
            {
                {
                    Ogre::CompositorPassClearDef *passClear = static_cast<Ogre::CompositorPassClearDef*>(targetDef->addPass(Ogre::PASS_CLEAR));
                    passClear->mColourValue = ImpostorPage::getImpostorBackgroundColor();
                }

                const float xDivFactor = 1.0f / IMPOSTOR_YAW_ANGLES;
                const float yDivFactor = 1.0f / IMPOSTOR_PITCH_ANGLES;
                for (int o = 0; o < IMPOSTOR_PITCH_ANGLES; ++o)
                {
                    for (int i = 0; i < IMPOSTOR_YAW_ANGLES; ++i)
                    {
                        Ogre::CompositorPassSceneDef *passScene = static_cast<Ogre::CompositorPassSceneDef*>(targetDef->addPass(Ogre::PASS_SCENE));
                        passScene->mVpLeft = (float)(i)* xDivFactor;
                        passScene->mVpTop = (float)(o)* yDivFactor;
                        passScene->mVpWidth = xDivFactor;
                        passScene->mVpHeight = yDivFactor;
                        passScene->mIdentifier = o * 1000 + i;
                        passScene->mMaterialScheme = "PagedGeometry_RenderBillboard";
                    }
                }
            }
        }

        workspaceDef = compositorManager->addWorkspaceDefinition(workspaceName);
        workspaceDef->connectOutput(nodeDef->getName(), 0);

    }
    else
    {

        workspaceDef  = compositorManager->getWorkspaceDefinition(workspaceName);
    }

    workSpace = compositorManager->addWorkspace(
        sceneMgr,
        renderTarget,
        renderCamera,
        workspaceName,
        false);


	//Set up scene node
    SceneNode* node = ImpostorTexture::group->getRenderNode();

    Ogre::Entity*e1 = entity->clone();
    e1->setStatic(false);


	Ogre::SceneNode *n1= node->createChildSceneNode();
    n1->attachObject(e1);
	n1->setPosition(-entityCenter);

    Entity *e2 = e1->clone();
	Ogre::SceneNode *n2= node->createChildSceneNode();
	n2->attachObject(e2);
	n2->setPosition(-entityCenter);

	//Set up camera FOV
	const Real objDist = entityRadius * 100;
	const Real nearDist = objDist - (entityRadius + 1);
	const Real farDist = objDist + (entityRadius + 1);

	renderCamera->setAspectRatio(1.0f);
	renderCamera->setFOVy(Math::ATan(0.5f*entityDiameter / objDist));
	renderCamera->setNearClipDistance(nearDist);
	renderCamera->setFarClipDistance(farDist);

	//Disable mipmapping (without this, masked textures look bad)
	MaterialManager *mm = MaterialManager::getSingletonPtr();
	FilterOptions oldMinFilter = mm->getDefaultTextureFiltering(FT_MIN);
	FilterOptions oldMagFilter = mm->getDefaultTextureFiltering(FT_MAG);
	FilterOptions oldMipFilter = mm->getDefaultTextureFiltering(FT_MIP);
	mm->setDefaultTextureFiltering(FO_POINT, FO_LINEAR, FO_NONE);

	//Disable fog
	FogMode oldFogMode = sceneMgr->getFogMode();
	ColourValue oldFogColor = sceneMgr->getFogColour();
	Real oldFogDensity = sceneMgr->getFogDensity();
	Real oldFogStart = sceneMgr->getFogStart();
	Real oldFogEnd = sceneMgr->getFogEnd();
	sceneMgr->setFog(FOG_NONE);

	// Get current status of the queue mode
	Ogre::SceneManager::SpecialCaseRenderQueueMode OldSpecialCaseRenderQueueMode = sceneMgr->getSpecialCaseRenderQueueMode();
	//Only render the entity
	sceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_INCLUDE);
    sceneMgr->addSpecialCaseRenderQueue(group->getParentPagedGeometry()->getRenderQueue() + 1);

    e1->setRenderQueueGroup(group->getParentPagedGeometry()->getRenderQueue() + 1);
    e2->setRenderQueueGroup(e1->getRenderQueueGroup());
    e1->setVisible(true);
	e2->setVisible(true);
    e1->setRenderingDistance(0);
	e2->setRenderingDistance(0);

	bool needsRegen = true;
#ifdef IMPOSTOR_FILE_SAVE

	String fileNameTGA = entity->getMesh()->getName()+".tga";
	String fileNameDDS = entity->getMesh()->getName()+".dds";

	//Attempt to load the pre-render file if allowed
	needsRegen = force;

    String tempdir = this->group->getParentPagedGeometry()->getTempdir();
	ResourceGroupManager::getSingleton().addResourceLocation(tempdir, "FileSystem", "BinFolder");
    ResourceGroupManager::getSingleton().initialiseResourceGroup("BinFolder");
    //messy
	if (!needsRegen)
	{
		try
		{
			if(Ogre::TextureManager::getSingleton().resourceExists(fileNameDDS))
			{
				texture = TextureManager::getSingleton().load(fileNameDDS, mResourceGroupName, TEX_TYPE_2D, MIP_UNLIMITED);
			}
			else
			{
				texture = TextureManager::getSingleton().load(fileNameTGA, mResourceGroupName, TEX_TYPE_2D, MIP_UNLIMITED);
			}
		}
		catch (...)
		{
			try
			{
				texture = TextureManager::getSingleton().load(fileNameTGA, mResourceGroupName, TEX_TYPE_2D, MIP_UNLIMITED);
			}
			catch (...)
			{
                try
		        {
			        if(Ogre::TextureManager::getSingleton().resourceExists(fileNameDDS))
			        {
				        texture = TextureManager::getSingleton().load(fileNameDDS, "BinFolder", TEX_TYPE_2D, MIP_UNLIMITED);
			        }
			        else
			        {
				        texture = TextureManager::getSingleton().load(fileNameTGA, "BinFolder", TEX_TYPE_2D, MIP_UNLIMITED);
			        }
		        }
		        catch (...)
		        {
				    needsRegen = true;
                }
			}
		}
	}
#endif

	if (needsRegen)
	{

        ImpostorRenderListener workSpaceListener(camNode, objDist, entityCenter);
        workSpace->setListener(&workSpaceListener);

        //manually update the workspace
        workSpace->_update();

#ifdef IMPOSTOR_FILE_SAVE
		//Save RTT to file with respecting the temp dir
	//	Ogre::DataStreamPtr dataStream=Ogre::ResourceGroupManager::getSingleton().createResource(fileNameTGA,mResourceGroupName,true,"*Trees");
		Ogre::StringVectorPtr locations= Ogre::ResourceGroupManager::getSingleton().findResourceLocation(mResourceGroupName,"*Trees");
        if (locations->size() == 0)
        {
            renderTarget->writeContentsToFile(tempdir+fileNameTGA);
            texture = TextureManager::getSingleton().load(tempdir + fileNameTGA, "BinFolder", TEX_TYPE_2D, MIP_UNLIMITED);
        }
        else
        {
            renderTarget->writeContentsToFile((*locations.getPointer())[0] + "/" + fileNameTGA);
            texture = TextureManager::getSingleton().load((*locations.getPointer())[0] + "/" + fileNameTGA, mResourceGroupName, TEX_TYPE_2D, MIP_UNLIMITED);
        }
		//Load the render into the appropriate texture view
		//Ogre::Image img;
		//img.load(fileNameTGA,mResourceGroupName);
		//img.resize( ImpostorPage::getImpostorResolution()* IMPOSTOR_YAW_ANGLES, ImpostorPage::getImpostorResolution()* IMPOSTOR_YAW_ANGLES,Ogre::Image::FILTER_BILINEAR);
		//img.save((*locations.getPointer())[0] + "/"+fileNameTGA);

		

#else
		texture = renderTexture;
#endif
        //texture = renderTexture;
		}

    sceneMgr->destroyEntity(e1);
	sceneMgr->destroyEntity(e2);

	sceneMgr->removeSpecialCaseRenderQueue(group->getParentPagedGeometry()->getRenderQueue() + 1);
	// Restore original state
	sceneMgr->setSpecialCaseRenderQueueMode(OldSpecialCaseRenderQueueMode);

	//Re-enable mipmapping
	mm->setDefaultTextureFiltering(oldMinFilter, oldMagFilter, oldMipFilter);

	//Re-enable fog
	sceneMgr->setFog(oldFogMode, oldFogColor, oldFogDensity, oldFogStart, oldFogEnd);

	//Delete camera
	//renderViewport->clear();
	//renderTarget->removeViewport(0);
	renderCamera->getSceneManager()->destroyCamera(renderCamera);

	//Delete scene node
	node->detachAllObjects();
	n2->detachAllObjects();
	n1->detachAllObjects();
	node->removeAndDestroyAllChildren();


#ifdef IMPOSTOR_FILE_SAVE
	//Delete RTT texture
	assert(!renderTexture.isNull());
	String texName2(renderTexture->getName());

	renderTexture.setNull();
	if (TextureManager::getSingletonPtr())
		TextureManager::getSingleton().remove(texName2);
#endif
	}
}

String ImpostorTexture::removeInvalidCharacters(String s)
{
	StringStream s2;

	for (uint32 i = 0; i < s.length(); ++i){
		char c = s[i];
		if (c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' || c == '\"' || c == '<' || c == '>' || c == '|'){
			s2 << '-';
		} else {
			s2 << c;
		}
	}

	return s2.str();
}

void ImpostorTexture::removeTexture(ImpostorTexture* Texture)
{
	//Search for an existing impostor texture, in case it was already deleted
	for(std::map<String, ImpostorTexture *>::iterator iter=selfList.begin();
		iter!=selfList.end(); ++iter)
	{
		if(iter->second==Texture)
		{
			delete Texture;
			return;
		}
	}
	// no need to anything if it was not found, chances are that it was already deleted
}

ImpostorTexture *ImpostorTexture::getTexture(ImpostorPage *group, Entity *entity)
{
	//Search for an existing impostor texture for the given entity
	String entityKey = ImpostorBatch::generateEntityKey(entity);
	std::map<String, ImpostorTexture *>::iterator iter;
	iter = selfList.find(entityKey);

	//If found..
	if (iter != selfList.end()){
		//Return it
		return iter->second;
	} else {
		if (group){
			//Otherwise, return a new texture
			return (new ImpostorTexture(group, entity));
		} else {
			//But if group is null, return null
			return NULL;
		}
	}
}
