History: Projective Decals
Source of version: 3 (current)
Copy to clipboard
{maketoc}
!!Introduction
This is a quick implementation of ((Intermediate Tutorial 6)). The class ''TerrainDecal'' represents a texture that is projected orthogonaly on top of the terrain from a given point and with a given size. This class will detect the terrain pages that are affected (limited to four, because only the four corners of the projection area are tested) and adds a pass to the materials of those pages.
If the Decal has to move, just call ''updatePosition()''. I think you can guess the meaning of ''updateSize()''. ;)
!!Using the class
on startup:
# ''init'' (doesn`t show the decal, just prepares)
# ''updatePosition''
# ''show''
after that, do whenever necessary:
* ''hide''
* ''show''
* ''updatePosition''
* ''updateSize''
!!Performance
One problem with this implementation is the performance: since every decal adds another pass to the underlaying terrain pages, the fps hit is quite hard as soon as multiple decals act at the same time. So I will be happy to accept optimizations! There are some promising new approaches in [http://www.ogre3d.org/phpBB2addons/viewtopic.php?t=498|this forum thread], which I will investigate further in the near future.
!!Code
{CODE(wrap="1", colors="c++")}class TerrainDecal
{
protected:
Ogre::Vector3 mPos; // center
Ogre::Vector2 mSize; // size of decal
std::string mTexture; // texture to apply
Ogre::SceneNode* mNode; // the projection node
Ogre::Frustum* mFrustum; // the projection frustum
Ogre::SceneManager* mSceneManager; // pointer to PLSM2
bool mVisible; // is the decal visible/active or not?
// info about materials that are receiving the decal
std::map<std::string,Ogre::Pass*> mTargets; // passes mapped by material names
bool isPosOnTerrain(Ogre::Vector3 pos)
{
// get the terrain boundaries
Ogre::AxisAlignedBox box;
mSceneManager->getOption("MapBoundaries",&box);
// check if pos is in box, ignore y
pos.y = 0;
return box.intersects(pos);
}
std::string getMaterialAtPosition(Ogre::Vector3 pos)
{
void* myOptionPtr = &pos;
// check if position is on battlefield
if( isPosOnTerrain(pos) )
{
mSceneManager->getOption ("getMaterialPageName", myOptionPtr);
std::string name = **static_cast<std::string**>(myOptionPtr);
return name;
}
else
return "";
}
void addMaterial(std::string matName)
{
// check if material is already decalled
if( mTargets.find(matName) != mTargets.end() )
{
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage("material should be added to decal but was already!");
return;
}
using namespace Ogre;
// get the material ptr
MaterialPtr mat = (MaterialPtr)MaterialManager::getSingleton().getByName(matName);
// create a new pass in the material to render the decal
Pass* pass = mat->getTechnique(0)->createPass();
// set up the decal's texture unit
TextureUnitState *texState = pass->createTextureUnitState(mTexture);
texState->setProjectiveTexturing(true, mFrustum);
texState->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
texState->setTextureFiltering(FO_POINT, FO_LINEAR, FO_NONE);
// set our pass to blend the decal over the model's regular texture
pass->setSceneBlending(SBT_TRANSPARENT_ALPHA);
pass->setDepthBias(1);
// set the decal to be self illuminated instead of lit by scene lighting
pass->setLightingEnabled(false);
// save pass in map
mTargets[matName] = pass;
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(std::string("added material to decal: ") + matName +
"(" + Ogre::StringConverter::toString(mTargets.size()) + " materials loaded)");
}
std::map<std::string,Ogre::Pass*>::iterator removeMaterial(std::string matName)
{
// remove our pass from the given material
mTargets[matName]->getParent()->removePass(mTargets[matName]->getIndex());
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(std::string("removed material from decal: ") + matName +
"(" + Ogre::StringConverter::toString(mTargets.size()-1) + " materials loaded)");
// remove from map
return mTargets.erase(mTargets.find(matName));
}
public:
TerrainDecal()
{
mVisible = false;
mNode = 0;
mFrustum = 0;
};
~TerrainDecal()
{
hide();
// delete frustum
mNode->detachAllObjects();
delete mFrustum;
// destroy node
mNode->getParentSceneNode()->removeAndDestroyChild(mNode->getName());
};
void init( Ogre::SceneManager* man, Ogre::Vector2 size, std::string tex )
{
using namespace Ogre;
// set SM
mSceneManager = man;
// init projective decal
// set up the main decal projection frustum
mFrustum = new Ogre::Frustum();
mNode = mSceneManager->getRootSceneNode()->createChildSceneNode();
mNode->attachObject(mFrustum);
mFrustum->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
mNode->setOrientation(Ogre::Quaternion(Ogre::Degree(90),Ogre::Vector3::UNIT_X));
// set given values
updateSize(size);
mTexture = tex; // texture to apply
// load the images for the decal and the filter
TextureManager::getSingleton().load
(mTexture, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, 1);
mVisible = false;
}
void show()
{
if( !mVisible )
{
mVisible = true;
updatePosition(mPos);
}
}
void hide()
{
if( mVisible )
{
// remove all added passes
while( !mTargets.empty() )
removeMaterial(mTargets.begin()->first);
mVisible = false;
}
}
void updatePosition( Ogre::Vector3 pos )
{
// don`t do anything if pos didn`t change
if( pos == mPos )
return;
// save the new position
mPos = pos;
mNode->setPosition(pos.x,pos.y+1000,pos.z);
// don`t show if invisible
if( !isVisible() )
return;
// check near pages (up to 4)
std::list<std::string> neededMaterials;
Ogre::Vector3 t;
// x high z high
t = Ogre::Vector3(mPos.x+mSize.x/2.0f,1000,mPos.z+mSize.y/2.0f);
neededMaterials.push_back(getMaterialAtPosition(t));
// x high z low
t = Ogre::Vector3(mPos.x+mSize.x/2.0f,1000,mPos.z-mSize.y/2.0f);
neededMaterials.push_back(getMaterialAtPosition(t));
// x low z low
t = Ogre::Vector3(mPos.x-mSize.x/2.0f,1000,mPos.z-mSize.y/2.0f);
neededMaterials.push_back(getMaterialAtPosition(t));
// x low z high
t = Ogre::Vector3(mPos.x-mSize.x/2.0f,1000,mPos.z+mSize.y/2.0f);
neededMaterials.push_back(getMaterialAtPosition(t));
// remove empties
neededMaterials.remove("");
// remove doubles
neededMaterials.unique();
// compare needed materials with used
// for every used material
std::map<std::string,Ogre::Pass*>::iterator used = mTargets.begin();
while(true)
{
// stop if we are through
if( used == mTargets.end() )
break;
// find in needed
std::list<std::string>::iterator needed =
std::find(neededMaterials.begin(),neededMaterials.end(),used->first);
if( needed == neededMaterials.end() )
{
// material is not needed any longer, so remove it
used = removeMaterial(used->first);
}
else
{
// remove it from needed list, bedause it is already loaded
neededMaterials.remove(used->first);
// go further
used++;
}
}
// add all remaining needed to used list
while( !neededMaterials.empty() )
{
addMaterial(neededMaterials.front());
neededMaterials.erase(neededMaterials.begin());
}
}
void updateSize(Ogre::Vector2 size)
{
if( mSize != size )
{
// save param
mSize = size;
// update aspect ratio
mFrustum->setAspectRatio(mSize.x/mSize.y);
// update height
mFrustum->setOrthoWindowHeight(mSize.y);
}
}
bool isVisible()
{
return mVisible;
}
};{CODE}
---
Alias: (alias(Projective_Decals))