I'm trying to place a "data block" which is text overlayed on a translucent background over the head of an NPC.
I have three questions enumerated at the bottom of this post. Images related to these questions are embedded below.
Details:
I've tried two approaches both of which use billboards to ensure that the text always faces the camera. I've had some success with each mechanism but have not refined either.
The first approach that I've steered away from is to use the MovableText function to overlay a billboardset which has an associated background image. I dont want to do this since the movabletext is largely independent of the background billboardset (though MovableText by itself works very well).
The approach that I'm focused on now uses a background texture that is common to all npcs (they all have the same translucent background) and then to create a unique foreground texture and material that is associated with the unique billboardset that is associated with each NPC.
Whenever I need to change the text that is placed above the NPC, I first write the background texture to the foreground texture, I then use the WriteToTexture function (that is presented within the Wiki) to overlay the text.
I'm trying to work out the kinks and the kink that I'm looking for help with here is that the text "disappears" (though the background image does not) when I rotate or back away from the NPC.
Once I work out some of the basic kinks then I'll work to "clean it up, and pretty it up".
Images:
The following is what the NPC data block looks like with the text showing correctly over the background.
The following is what the datablock looks like if I back away from the npc slightly. Notice that the NPC name (Joe) is gone. This same effect happens if I just rotate the camera slightly
Code:
A Datablock is instantiated for each NPC. Ultimately there will be hundreds of such instantiated NPCS. The following is my Constructor code for creating the Datablock:
Code: Select all
Datablock::Datablock(string name, string caption, Ogre::Font *font, Ogre::ColourValue color) : Ogre::BillboardSet(name + ".bb")
{
m_db_name = name + ".bb";
setBillboardOrigin(Ogre::BillboardOrigin::BBO_BOTTOM_LEFT);
setMaterialName("Datablock"); // Use a material file
createBillboard(0,0,0);
std::string txt_name = m_db_name + ".txt";
std::string mat_name = m_db_name + ".mat";
Ogre::TextureManager *text_mgr = Ogre::TextureManager::getSingletonPtr();
//Ogre::ResourcePtr rp = text_mgr->getByName("bounding_box.png");
mBackgroundTexture = (Ogre::Texture *) text_mgr->getByName("bounding_box.png").getPointer();
mForegroundTexture = (Ogre::TexturePtr) text_mgr->createManual(txt_name, "General",
Ogre::TEX_TYPE_2D, 256, 256, Ogre::MIP_UNLIMITED, Ogre::PF_A8R8G8B8, Ogre::TU_DEFAULT);
mMaterial = Ogre::MaterialManager::getSingleton().create(mat_name, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
mMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(txt_name);
mMaterial->getTechnique(0)->getPass(0)->setSceneBlending(Ogre::SBF_SOURCE_ALPHA, Ogre::SBF_ONE_MINUS_SOURCE_ALPHA);
mMaterial->setDepthBias(false);
mMaterial->setDepthCheckEnabled(false);
mMaterial->setDepthWriteEnabled(true);
setMaterialName(mat_name);
mFont = ConfigurationManager::getSingleton().getFont();
assert(mFont);
//this->showOnTop(true);
m_background_state = true;
m_string.assign(caption);
}
Code: Select all
void Datablock::Set(string string)
{
mForegroundTexture->getBuffer()->blit(mBackgroundTexture->getBuffer());
writeToTexture(string, mForegroundTexture, Ogre::Image::Box(0,127,255,255), mFont, Ogre::ColourValue(0.0, 1.0, 0.0, 1.0), 'c', true);
m_string = string;
}
Code: Select all
void Datablock::writeToTexture(std::string &str, Ogre::TexturePtr destTexture, Ogre::Image::Box destRectangle, Ogre::Font* font, Ogre::ColourValue &color, char justify, bool wordwrap)
{
using namespace Ogre;
if (destTexture->getHeight() < destRectangle.bottom)
destRectangle.bottom = destTexture->getHeight();
if (destTexture->getWidth() < destRectangle.right)
destRectangle.right = destTexture->getWidth();
if (!font->isLoaded())
font->load();
TexturePtr fontTexture = (TexturePtr) TextureManager::getSingleton().getByName(font->getMaterial()->getTechnique(0)->getPass(0)->getTextureUnitState(0)->getTextureName());
HardwarePixelBufferSharedPtr fontBuffer = fontTexture->getBuffer();
HardwarePixelBufferSharedPtr destBuffer = destTexture->getBuffer();
//When locking destBuffer with HBL_NORMAL there are somn issues with OGL but but with HBL_READ_ONLY it works fine -- very strange .. isn't it?
PixelBox destPb = destBuffer->lock(destRectangle,HardwareBuffer::HBL_READ_ONLY);
PixelBox fontPb = fontBuffer->lock(Image::Box(0,0,fontTexture->getWidth(),fontTexture->getHeight()),HardwareBuffer::HBL_READ_ONLY);
uint8* fontData = static_cast<uint8*>( fontPb.data );
uint8* destData = static_cast<uint8*>( destPb.data );
const size_t fontPixelSize = PixelUtil::getNumElemBytes(fontPb.format);
const size_t destPixelSize = PixelUtil::getNumElemBytes(destPb.format);
const size_t fontRowPitchBytes = fontPb.rowPitch * fontPixelSize;
const size_t destRowPitchBytes = destPb.rowPitch * destPixelSize;
Box *GlyphTexCoords;
GlyphTexCoords = new Box[str.size()];
Real u1,v1,u2,v2;
size_t charheight = 0;
size_t charwidth = 0;
for(unsigned int i = 0; i < str.size(); i++)
{
if ((str[i] != '\t') && (str[i] != '\n') && (str[i] != ' '))
{
font->getGlyphTexCoords(OgreChar(str[i]),u1,v1,u2,v2);
GlyphTexCoords[i].left = u1 * fontTexture->getSrcWidth();
GlyphTexCoords[i].top = v1 * fontTexture->getSrcHeight();
GlyphTexCoords[i].right = u2 * fontTexture->getSrcWidth();
GlyphTexCoords[i].bottom = v2 * fontTexture->getSrcHeight();
if (GlyphTexCoords[i].getHeight() > charheight)
charheight = GlyphTexCoords[i].getHeight();
if (GlyphTexCoords[i].getWidth() > charwidth)
charwidth = GlyphTexCoords[i].getWidth();
}
}
size_t cursorX = 0;
size_t cursorY = 0;
size_t lineend = destRectangle.getWidth();
bool carriagreturn = true;
for (unsigned int strindex = 0; strindex < str.size(); strindex++)
{
switch(str[strindex])
{
case ' ': cursorX += charwidth; break;
case '\t':cursorX += charwidth * 3; break;
case '\n':cursorY += charheight; carriagreturn = true; break;
default:
{
//wrapping
if ((cursorX + GlyphTexCoords[strindex].getWidth()> lineend) && !carriagreturn )
{
cursorY += charheight;
carriagreturn = true;
}
//justify
if (carriagreturn)
{
size_t l = strindex;
size_t textwidth = 0;
size_t wordwidth = 0;
while( (l < str.size() ) && (str[l] != '\n)'))
{
wordwidth = 0;
switch (str[l])
{
case ' ': wordwidth = charwidth; ++l; break;
case '\t': wordwidth = charwidth *3; ++l; break;
case '\n': l = str.size();
}
if (wordwrap)
while((l < str.size()) && (str[l] != ' ') && (str[l] != '\t') && (str[l] != '\n'))
{
wordwidth += GlyphTexCoords[l].getWidth();
++l;
}
else
{
wordwidth += GlyphTexCoords[l].getWidth();
l++;
}
if ((textwidth + wordwidth) <= destRectangle.getWidth())
textwidth += (wordwidth);
else
break;
}
if ((textwidth == 0) && (wordwidth > destRectangle.getWidth()))
textwidth = destRectangle.getWidth();
switch (justify)
{
case 'c': cursorX = (destRectangle.getWidth() - textwidth)/2;
lineend = destRectangle.getWidth() - cursorX;
break;
case 'r': cursorX = (destRectangle.getWidth() - textwidth);
lineend = destRectangle.getWidth();
break;
default: cursorX = 0;
lineend = textwidth;
break;
}
carriagreturn = false;
}
//abort - net enough space to draw
if ((cursorY + charheight) > destRectangle.getHeight())
goto stop;
//draw pixel by pixel
for (size_t i = 0; i <= GlyphTexCoords[strindex].getHeight(); i++ )
for (size_t j = 0; j <= GlyphTexCoords[strindex].getWidth(); j++)
{
float alpha = color.a * (fontData[(i + GlyphTexCoords[strindex].top) * fontRowPitchBytes + (j + GlyphTexCoords[strindex].left) * fontPixelSize +1 ] / 255.0);
float invalpha = 1.0 - alpha;
size_t offset = (i + cursorY) * destRowPitchBytes + (j + cursorX) * destPixelSize;
ColourValue pix;
PixelUtil::unpackColour(&pix,destPb.format,&destData[offset]);
pix = (pix * invalpha) + (color * alpha);
PixelUtil::packColour(pix,destPb.format,&destData[offset]);
}
cursorX += GlyphTexCoords[strindex].getWidth();
}//default
}//switch
}//for
stop:
delete[] GlyphTexCoords;
destBuffer->unlock();
fontBuffer->unlock();
}
1] Why is the NPC name disappearing when I rotate the camera or back the camera away from the displayed datablock?
2] One thing I'm not really comfortable with in this approach is that if I have 1000 NPC's then this approach will require me to create 1000 billboardsets, 1000 materials, and 1000 foreground textures. My discomfort may be largely due to my inexperience with these mechanisms from a performance perspective.
Is my basic approach reasonable? Will there be a problem with my approach from a performance perspective?
3] Additional thoughts on the best (or alternative) way to do what I'm trying to do would also be beneficial.