Files
OpenSceneGraph/src/osgText/Text.cpp

1387 lines
44 KiB
C++

/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#include <osgText/Text>
#include <osg/Math>
#include <osg/GL>
#include <osg/Notify>
#include <osg/PolygonOffset>
#include <osg/TexEnv>
#include <osg/io_utils>
#include <osgUtil/CullVisitor>
#include <osgDB/ReadFile>
#include <sstream>
#include <iomanip>
using namespace osg;
using namespace osgText;
Text::Text():
_shaderTechnique(GREYSCALE),
_enableDepthWrites(true),
_backdropType(NONE),
_backdropHorizontalOffset(0.07f),
_backdropVerticalOffset(0.07f),
_backdropColor(0.0f, 0.0f, 0.0f, 1.0f),
_colorGradientMode(SOLID),
_colorGradientTopLeft(1.0f, 0.0f, 0.0f, 1.0f),
_colorGradientBottomLeft(0.0f, 1.0f, 0.0f, 1.0f),
_colorGradientBottomRight(0.0f, 0.0f, 1.0f, 1.0f),
_colorGradientTopRight(1.0f, 1.0f, 1.0f, 1.0f)
{
_supportsVertexBufferObjects = true;
char* ptr = 0;
if ((ptr = getenv("OSG_SDF_TEXT")) != 0)
{
_shaderTechnique = ALL_FEATURES;
}
else if ((ptr = getenv("OSG_GREYSCALE_TEXT")) != 0)
{
_shaderTechnique = GREYSCALE;
}
assignStateSet();
}
Text::Text(const Text& text,const osg::CopyOp& copyop):
osgText::TextBase(text,copyop),
_shaderTechnique(text._shaderTechnique),
_enableDepthWrites(text._enableDepthWrites),
_backdropType(text._backdropType),
_backdropHorizontalOffset(text._backdropHorizontalOffset),
_backdropVerticalOffset(text._backdropVerticalOffset),
_backdropColor(text._backdropColor),
_colorGradientMode(text._colorGradientMode),
_colorGradientTopLeft(text._colorGradientTopLeft),
_colorGradientBottomLeft(text._colorGradientBottomLeft),
_colorGradientBottomRight(text._colorGradientBottomRight),
_colorGradientTopRight(text._colorGradientTopRight)
{
computeGlyphRepresentation();
}
Text::~Text()
{
}
void Text::setShaderTechnique(ShaderTechnique technique)
{
if (_shaderTechnique==technique) return;
_shaderTechnique = technique;
assignStateSet();
}
osg::StateSet* Text::createStateSet()
{
Font* activeFont = getActiveFont();
if (!activeFont) return 0;
Font::StateSets& statesets = activeFont->getCachedStateSets();
std::stringstream ss;
ss<<std::fixed<<std::setprecision(3);
osg::StateSet::DefineList defineList;
if (_backdropType!=NONE)
{
ss.str("");
ss << "vec4("<<_backdropColor.r()<<", "<<_backdropColor.g()<<", "<<_backdropColor.b()<<", "<<_backdropColor.a()<<")";
defineList["BACKDROP_COLOR"] = osg::StateSet::DefinePair(ss.str(), osg::StateAttribute::ON);
if (_backdropType==OUTLINE)
{
ss.str("");
ss <<_backdropHorizontalOffset;
defineList["OUTLINE"] = osg::StateSet::DefinePair(ss.str(), osg::StateAttribute::ON);
}
else
{
osg::Vec2 offset(_backdropHorizontalOffset, _backdropVerticalOffset);
switch(_backdropType)
{
case(DROP_SHADOW_BOTTOM_RIGHT) : offset.set(_backdropHorizontalOffset, -_backdropVerticalOffset); break;
case(DROP_SHADOW_CENTER_RIGHT) : offset.set(_backdropHorizontalOffset, 0.0f); break;
case(DROP_SHADOW_TOP_RIGHT) : offset.set(_backdropHorizontalOffset, _backdropVerticalOffset); break;
case(DROP_SHADOW_BOTTOM_CENTER) : offset.set(0.0f, -_backdropVerticalOffset); break;
case(DROP_SHADOW_TOP_CENTER) : offset.set(0.0f, _backdropVerticalOffset); break;
case(DROP_SHADOW_BOTTOM_LEFT) : offset.set(-_backdropHorizontalOffset, -_backdropVerticalOffset); break;
case(DROP_SHADOW_CENTER_LEFT) : offset.set(-_backdropHorizontalOffset, 0.0f); break;
case(DROP_SHADOW_TOP_LEFT) : offset.set(-_backdropHorizontalOffset, _backdropVerticalOffset); break;
default : break;
}
ss.str("");
ss << "vec2("<<offset.x()<<", "<<offset.y()<<")";
defineList["SHADOW"] = osg::StateSet::DefinePair(ss.str(), osg::StateAttribute::ON);
}
}
if (_shaderTechnique!=GREYSCALE)
{
ss<<std::fixed<<std::setprecision(1);
ss.str("");
ss << float(_fontSize.second);
defineList["GLYPH_DIMENSION"] = osg::StateSet::DefinePair(ss.str(), osg::StateAttribute::ON);
ss.str("");
ss << float(activeFont->getTextureWidthHint());
defineList["TEXTURE_DIMENSION"] = osg::StateSet::DefinePair(ss.str(), osg::StateAttribute::ON);
defineList["SIGNED_DISTNACE_FIELD"] = osg::StateSet::DefinePair("1", osg::StateAttribute::ON);
}
#if 0
OSG_NOTICE<<"Text::createStateSet() defines:"<<defineList.size()<<std::endl;
for(osg::StateSet::DefineList::iterator itr = defineList.begin();
itr != defineList.end();
++itr)
{
OSG_NOTICE<<" define["<<itr->first<<"] = "<<itr->second.first<<std::endl;
}
#endif
if (!statesets.empty())
{
for(Font::StateSets::iterator itr = statesets.begin();
itr != statesets.end();
++itr)
{
if ((*itr)->getDefineList()==defineList)
{
// OSG_NOTICE<<"Text::createStateSet() : Matched DefineList, return StateSet "<<itr->get()<<std::endl;
return itr->get();
}
else
{
}
}
}
OSG_NOTICE<<"Text::createStateSet() : Not Matched DefineList, creating new StateSet"<<std::endl;
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
stateset->setDefineList(defineList);
statesets.push_back(stateset.get());
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
OSG_NOTICE<<"Text::createStateSet() ShaderTechnique="<<_shaderTechnique<<std::endl;
#if defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
osg::DisplaySettings::ShaderHint shaderHint = osg::DisplaySettings::instance()->getShaderHint();
if (_shaderTechnique==GREYSCALE && shaderHint==osg::DisplaySettings::SHADER_NONE)
{
OSG_NOTICE<<"Font::Font() Fixed function pipeline"<<std::endl;
stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
return stateset.release();
}
#endif
// set up the StateSet to use shaders
stateset->addUniform(new osg::Uniform("glyphTexture", 0));
osg::ref_ptr<osg::Program> program = new osg::Program;
stateset->setAttributeAndModes(program.get());
{
OSG_NOTICE<<"Using shaders/text.vert"<<std::endl;
#include "shaders/text_vert.cpp"
program->addShader(osgDB::readRefShaderFileWithFallback(osg::Shader::VERTEX, "shaders/text.vert", text_vert));
}
if (_shaderTechnique==GREYSCALE)
{
OSG_NOTICE<<"Using shaders/text_greyscale.frag"<<std::endl;
#include "shaders/text_greyscale_frag.cpp"
program->addShader(osgDB::readRefShaderFileWithFallback(osg::Shader::FRAGMENT, "shaders/text_greyscale.frag", text_greyscale_frag));
}
else
{
OSG_NOTICE<<"Using shaders/text_sdf.frag"<<std::endl;
#include "shaders/text_sdf_frag.cpp"
program->addShader(osgDB::readRefShaderFileWithFallback(osg::Shader::FRAGMENT, "shaders/text_sdf.frag", text_sdf_frag));
}
return stateset.release();
}
Font* Text::getActiveFont()
{
return _font.valid() ? _font.get() : Font::getDefaultFont().get();
}
const Font* Text::getActiveFont() const
{
return _font.valid() ? _font.get() : Font::getDefaultFont().get();
}
String::iterator Text::computeLastCharacterOnLine(osg::Vec2& cursor, String::iterator first,String::iterator last)
{
Font* activefont = getActiveFont();
if (!activefont) return last;
float hr = _characterHeight;
float wr = hr/getCharacterAspectRatio();
bool kerning = true;
unsigned int previous_charcode = 0;
String::iterator lastChar = first;
for(bool outOfSpace=false;lastChar!=last;++lastChar)
{
unsigned int charcode = *lastChar;
if (charcode=='\n')
{
return lastChar;
}
Glyph* glyph = activefont->getGlyph(_fontSize, charcode);
if (glyph)
{
float width = (float)(glyph->getWidth()) * wr;
if (_layout==RIGHT_TO_LEFT)
{
cursor.x() -= glyph->getHorizontalAdvance() * wr;
}
// adjust cursor position w.r.t any kerning.
if (kerning && previous_charcode)
{
switch(_layout)
{
case LEFT_TO_RIGHT:
{
osg::Vec2 delta(activefont->getKerning(_fontSize, previous_charcode, charcode, _kerningType));
cursor.x() += delta.x() * wr;
cursor.y() += delta.y() * hr;
break;
}
case RIGHT_TO_LEFT:
{
osg::Vec2 delta(activefont->getKerning(_fontSize, charcode, previous_charcode, _kerningType));
cursor.x() -= delta.x() * wr;
cursor.y() -= delta.y() * hr;
break;
}
case VERTICAL:
break; // no kerning when vertical.
} // check to see if we are still within line if not move to next line.
}
switch(_layout)
{
case LEFT_TO_RIGHT:
{
if (_maximumWidth>0.0f && cursor.x()+width>_maximumWidth) outOfSpace=true;
if(_maximumHeight>0.0f && cursor.y()<-_maximumHeight) outOfSpace=true;
break;
}
case RIGHT_TO_LEFT:
{
if (_maximumWidth>0.0f && cursor.x()<-_maximumWidth) outOfSpace=true;
if(_maximumHeight>0.0f && cursor.y()<-_maximumHeight) outOfSpace=true;
break;
}
case VERTICAL:
if (_maximumHeight>0.0f && cursor.y()<-_maximumHeight) outOfSpace=true;
break;
}
// => word boundary detection & wrapping
if (outOfSpace) break;
// move the cursor onto the next character.
switch(_layout)
{
case LEFT_TO_RIGHT: cursor.x() += glyph->getHorizontalAdvance() * wr; break;
case VERTICAL: cursor.y() -= glyph->getVerticalAdvance() *hr; break;
case RIGHT_TO_LEFT: break; // nop.
}
previous_charcode = charcode;
}
}
// word boundary detection & wrapping
if (lastChar!=last)
{
String::iterator lastValidChar = lastChar;
String::iterator prevChar;
while (lastValidChar != first){
prevChar = lastValidChar - 1;
// last char is after a hyphen
if(*lastValidChar == '-')
return lastValidChar + 1;
// last char is start of whitespace
if((*lastValidChar == ' ' || *lastValidChar == '\n') && (*prevChar != ' ' && *prevChar != '\n'))
return lastValidChar;
// Subtract off glyphs from the cursor position (to correctly center text)
if(*prevChar != '-')
{
Glyph* glyph = activefont->getGlyph(_fontSize, *prevChar);
if (glyph)
{
switch(_layout)
{
case LEFT_TO_RIGHT: cursor.x() -= glyph->getHorizontalAdvance() * wr; break;
case VERTICAL: cursor.y() += glyph->getVerticalAdvance() * hr; break;
case RIGHT_TO_LEFT: break; // nop.
}
}
}
lastValidChar = prevChar;
}
}
return lastChar;
}
void Text::addGlyphQuad(Glyph* glyph, const osg::Vec2& minc, const osg::Vec2& maxc, const osg::Vec2& mintc, const osg::Vec2& maxtc)
{
// set up the coords of the quad
const Glyph::TextureInfo* info = glyph->getOrCreateTextureInfo(_shaderTechnique);
GlyphTexture* glyphTexture = info ? info->texture : 0;
GlyphQuads& glyphquad = _textureGlyphQuadMap[glyphTexture];
glyphquad._glyphs.push_back(glyph);
osg::DrawElements* primitives = glyphquad._primitives.get();
if (!primitives)
{
unsigned int maxIndices = _text.size()*4;
if (maxIndices>=16384) primitives = new osg::DrawElementsUInt(GL_TRIANGLES);
else primitives = new osg::DrawElementsUShort(GL_TRIANGLES);
primitives->setBufferObject(_ebo.get());
glyphquad._primitives = primitives;
}
unsigned int lt = addCoord(osg::Vec2(minc.x(), maxc.y()));
unsigned int lb = addCoord(osg::Vec2(minc.x(), minc.y()));
unsigned int rb = addCoord(osg::Vec2(maxc.x(), minc.y()));
unsigned int rt = addCoord(osg::Vec2(maxc.x(), maxc.y()));
// set up the tex coords of the quad
addTexCoord(osg::Vec2(mintc.x(), maxtc.y()));
addTexCoord(osg::Vec2(mintc.x(), mintc.y()));
addTexCoord(osg::Vec2(maxtc.x(), mintc.y()));
addTexCoord(osg::Vec2(maxtc.x(), maxtc.y()));
primitives->addElement(lt);
primitives->addElement(lb);
primitives->addElement(rb);
primitives->addElement(lt);
primitives->addElement(rb);
primitives->addElement(rt);
primitives->dirty();
}
void Text::computeGlyphRepresentation()
{
Font* activefont = getActiveFont();
if (!activefont) return;
if (!_coords) { _coords = new osg::Vec3Array(osg::Array::BIND_PER_VERTEX); _coords->setBufferObject(_vbo.get()); }
else _coords->clear();
if (!_colorCoords) { _colorCoords = new osg::Vec4Array(osg::Array::BIND_PER_VERTEX); _colorCoords->setBufferObject(_vbo.get()); }
else _colorCoords->clear();
if (!_texcoords) { _texcoords = new osg::Vec2Array(osg::Array::BIND_PER_VERTEX); _texcoords->setBufferObject(_vbo.get()); }
else _texcoords->clear();
for(TextureGlyphQuadMap::iterator itr = _textureGlyphQuadMap.begin();
itr != _textureGlyphQuadMap.end();
++itr)
{
GlyphQuads& glyphquads = itr->second;
glyphquads._glyphs.clear();
if (glyphquads._primitives.valid())
{
glyphquads._primitives->resizeElements(0);
glyphquads._primitives->dirty();
}
}
_lineCount = 0;
if (_text.empty())
{
_textBB.set(0,0,0,0,0,0);//no size text
computePositions(); //to reset the origin
return;
}
//OpenThreads::ScopedLock<Font::FontMutex> lock(*(activefont->getSerializeFontCallsMutex()));
// initialize bounding box, it will be expanded during glyph position calculation
_textBB.init();
osg::Vec2 startOfLine_coords(0.0f,0.0f);
osg::Vec2 cursor(startOfLine_coords);
osg::Vec2 local(0.0f,0.0f);
unsigned int previous_charcode = 0;
unsigned int linelength = 0;
bool horizontal = _layout!=VERTICAL;
bool kerning = true;
float hr = _characterHeight;
float wr = hr/getCharacterAspectRatio();
for(String::iterator itr=_text.begin();
itr!=_text.end();
)
{
// record the start of the current line
String::iterator startOfLine_itr = itr;
// find the end of the current line.
osg::Vec2 endOfLine_coords(cursor);
String::iterator endOfLine_itr = computeLastCharacterOnLine(endOfLine_coords, itr,_text.end());
linelength = endOfLine_itr - startOfLine_itr;
// Set line position to correct alignment.
switch(_layout)
{
case LEFT_TO_RIGHT:
{
switch(_alignment)
{
// nothing to be done for these
//case LEFT_TOP:
//case LEFT_CENTER:
//case LEFT_BOTTOM:
//case LEFT_BASE_LINE:
//case LEFT_BOTTOM_BASE_LINE:
// break;
case CENTER_TOP:
case CENTER_CENTER:
case CENTER_BOTTOM:
case CENTER_BASE_LINE:
case CENTER_BOTTOM_BASE_LINE:
cursor.x() = (cursor.x() - endOfLine_coords.x()) * 0.5f;
break;
case RIGHT_TOP:
case RIGHT_CENTER:
case RIGHT_BOTTOM:
case RIGHT_BASE_LINE:
case RIGHT_BOTTOM_BASE_LINE:
cursor.x() = cursor.x() - endOfLine_coords.x();
break;
default:
break;
}
break;
}
case RIGHT_TO_LEFT:
{
switch(_alignment)
{
case LEFT_TOP:
case LEFT_CENTER:
case LEFT_BOTTOM:
case LEFT_BASE_LINE:
case LEFT_BOTTOM_BASE_LINE:
cursor.x() = 2*cursor.x() - endOfLine_coords.x();
break;
case CENTER_TOP:
case CENTER_CENTER:
case CENTER_BOTTOM:
case CENTER_BASE_LINE:
case CENTER_BOTTOM_BASE_LINE:
cursor.x() = cursor.x() + (cursor.x() - endOfLine_coords.x()) * 0.5f;
break;
// nothing to be done for these
//case RIGHT_TOP:
//case RIGHT_CENTER:
//case RIGHT_BOTTOM:
//case RIGHT_BASE_LINE:
//case RIGHT_BOTTOM_BASE_LINE:
// break;
default:
break;
}
break;
}
case VERTICAL:
{
switch(_alignment)
{
// TODO: current behaviour top baselines lined up in both cases - need to implement
// top of characters alignment - Question is this necessary?
// ... otherwise, nothing to be done for these 6 cases
//case LEFT_TOP:
//case CENTER_TOP:
//case RIGHT_TOP:
// break;
//case LEFT_BASE_LINE:
//case CENTER_BASE_LINE:
//case RIGHT_BASE_LINE:
// break;
case LEFT_CENTER:
case CENTER_CENTER:
case RIGHT_CENTER:
cursor.y() = cursor.y() + (cursor.y() - endOfLine_coords.y()) * 0.5f;
break;
case LEFT_BOTTOM_BASE_LINE:
case CENTER_BOTTOM_BASE_LINE:
case RIGHT_BOTTOM_BASE_LINE:
cursor.y() = cursor.y() - (linelength * _characterHeight);
break;
case LEFT_BOTTOM:
case CENTER_BOTTOM:
case RIGHT_BOTTOM:
cursor.y() = 2*cursor.y() - endOfLine_coords.y();
break;
default:
break;
}
break;
}
}
if (itr!=endOfLine_itr)
{
for(;itr!=endOfLine_itr;++itr)
{
unsigned int charcode = *itr;
Glyph* glyph = activefont->getGlyph(_fontSize, charcode);
if (glyph)
{
float width = (float)(glyph->getWidth()) * wr;
float height = (float)(glyph->getHeight()) * hr;
if (_layout==RIGHT_TO_LEFT)
{
cursor.x() -= glyph->getHorizontalAdvance() * wr;
}
// adjust cursor position w.r.t any kerning.
if (kerning && previous_charcode)
{
switch(_layout)
{
case LEFT_TO_RIGHT:
{
osg::Vec2 delta(activefont->getKerning(_fontSize, previous_charcode, charcode, _kerningType));
cursor.x() += delta.x() * wr;
cursor.y() += delta.y() * hr;
break;
}
case RIGHT_TO_LEFT:
{
osg::Vec2 delta(activefont->getKerning(_fontSize, charcode, previous_charcode, _kerningType));
cursor.x() -= delta.x() * wr;
cursor.y() -= delta.y() * hr;
break;
}
case VERTICAL:
break; // no kerning when vertical.
}
}
local = cursor;
osg::Vec2 bearing(horizontal?glyph->getHorizontalBearing():glyph->getVerticalBearing());
local.x() += bearing.x() * wr;
local.y() += bearing.y() * hr;
const Glyph::TextureInfo* info = glyph->getOrCreateTextureInfo(_shaderTechnique);
if (info)
{
// Adjust coordinates and texture coordinates to avoid
// clipping the edges of antialiased characters.
osg::Vec2 mintc = info->minTexCoord;
osg::Vec2 maxtc = info->maxTexCoord;
osg::Vec2 vDiff = maxtc - mintc;
float texelMargin = info->texelMargin;
float fHorizTCMargin = texelMargin / info->texture->getTextureWidth();
float fVertTCMargin = texelMargin / info->texture->getTextureHeight();
float fHorizQuadMargin = vDiff.x() == 0.0f ? 0.0f : width * fHorizTCMargin / vDiff.x();
float fVertQuadMargin = vDiff.y() == 0.0f ? 0.0f : height * fVertTCMargin / vDiff.y();
mintc.x() -= fHorizTCMargin;
mintc.y() -= fVertTCMargin;
maxtc.x() += fHorizTCMargin;
maxtc.y() += fVertTCMargin;
osg::Vec2 minc = local+osg::Vec2(0.0f-fHorizQuadMargin,0.0f-fVertQuadMargin);
osg::Vec2 maxc = local+osg::Vec2(width+fHorizQuadMargin,height+fVertQuadMargin);
addGlyphQuad(glyph, minc, maxc, mintc, maxtc);
// move the cursor onto the next character.
// also expand bounding box
switch(_layout)
{
case LEFT_TO_RIGHT:
cursor.x() += glyph->getHorizontalAdvance() * wr;
_textBB.expandBy(osg::Vec3(minc.x(), minc.y(), 0.0f)); //lower left corner
_textBB.expandBy(osg::Vec3(maxc.x(), maxc.y(), 0.0f)); //upper right corner
break;
case VERTICAL:
cursor.y() -= glyph->getVerticalAdvance() * hr;
_textBB.expandBy(osg::Vec3(minc.x(),maxc.y(),0.0f)); //upper left corner
_textBB.expandBy(osg::Vec3(maxc.x(),minc.y(),0.0f)); //lower right corner
break;
case RIGHT_TO_LEFT:
_textBB.expandBy(osg::Vec3(maxc.x(),minc.y(),0.0f)); //lower right corner
_textBB.expandBy(osg::Vec3(minc.x(),maxc.y(),0.0f)); //upper left corner
break;
}
}
else
{
OSG_NOTICE<<"No TextureInfo for "<<charcode<<std::endl;
}
previous_charcode = charcode;
}
}
// skip over spaces and return.
while (itr != _text.end() && *itr==' ') ++itr;
if (itr != _text.end() && *itr=='\n') ++itr;
}
else
{
++itr;
}
// move to new line.
switch(_layout)
{
case LEFT_TO_RIGHT:
{
startOfLine_coords.y() -= _characterHeight * (1.0 + _lineSpacing);
cursor = startOfLine_coords;
previous_charcode = 0;
_lineCount++;
break;
}
case RIGHT_TO_LEFT:
{
startOfLine_coords.y() -= _characterHeight * (1.0 + _lineSpacing);
cursor = startOfLine_coords;
previous_charcode = 0;
_lineCount++;
break;
}
case VERTICAL:
{
startOfLine_coords.x() += _characterHeight/getCharacterAspectRatio() * (1.0 + _lineSpacing);
cursor = startOfLine_coords;
previous_charcode = 0;
// because _lineCount is the max vertical no. of characters....
_lineCount = (_lineCount >linelength)?_lineCount:linelength;
}
break;
}
}
computePositions();
computeColorGradients();
// set up the vertices for any boundinbox or alignment decoration
setupDecoration();
}
// Returns false if there are no glyphs and the width/height values are invalid.
// Also sets avg_width and avg_height to 0.0f if the value is invalid.
// This method is used several times in a loop for the same object which will produce the same values.
// Further optimization may try saving these values instead of recomputing them.
bool Text::computeAverageGlyphWidthAndHeight(float& avg_width, float& avg_height) const
{
float width = 0.0f;
float height = 0.0f;
float running_width = 0.0f;
float running_height = 0.0f;
avg_width = 0.0f;
avg_height = 0.0f;
int counter = 0;
unsigned int i;
bool is_valid_size = true;
for (i = 0; i < _coords->size(); i += 4)
{
width = (*_coords)[i + 2].x() - (*_coords)[i].x();
height = (*_coords)[i].y() - (*_coords)[i + 1].y();
running_width += width;
running_height += height;
counter++;
}
if(0 == counter)
{
is_valid_size = false;
}
else
{
avg_width = running_width/counter;
avg_height = running_height/counter;
}
return is_valid_size;
}
void Text::computePositionsImplementation()
{
TextBase::computePositionsImplementation();
computeBackdropBoundingBox();
computeBoundingBoxMargin();
}
// This method adjusts the bounding box to account for the expanded area caused by the backdrop.
// This assumes that the bounding box has already been computed for the text without the backdrop.
void Text::computeBackdropBoundingBox()
{
if(_backdropType == NONE)
{
return;
}
float avg_width = 0.0f;
float avg_height = 0.0f;
bool is_valid_size;
// FIXME: OPTIMIZE: It is possible that this value has already been computed before
// from previous calls to this function. This might be worth optimizing.
is_valid_size = computeAverageGlyphWidthAndHeight(avg_width, avg_height);
// Finally, we have one more issue to deal with.
// Now that the text takes more space, we need
// to adjust the size of the bounding box.
if((!_textBB.valid() || !is_valid_size))
{
return;
}
// Finally, we have one more issue to deal with.
// Now that the text takes more space, we need
// to adjust the size of the bounding box.
switch(_backdropType)
{
case DROP_SHADOW_BOTTOM_RIGHT:
{
_textBB.set(
_textBB.xMin(),
_textBB.yMin() - avg_height * _backdropVerticalOffset,
_textBB.zMin(),
_textBB.xMax() + avg_width * _backdropHorizontalOffset,
_textBB.yMax(),
_textBB.zMax()
);
break;
}
case DROP_SHADOW_CENTER_RIGHT:
{
_textBB.set(
_textBB.xMin(),
_textBB.yMin(),
_textBB.zMin(),
_textBB.xMax() + avg_width * _backdropHorizontalOffset,
_textBB.yMax(),
_textBB.zMax()
);
break;
}
case DROP_SHADOW_TOP_RIGHT:
{
_textBB.set(
_textBB.xMin(),
_textBB.yMin(),
_textBB.zMin(),
_textBB.xMax() + avg_width * _backdropHorizontalOffset,
_textBB.yMax() + avg_height * _backdropVerticalOffset,
_textBB.zMax()
);
break;
}
case DROP_SHADOW_BOTTOM_CENTER:
{
_textBB.set(
_textBB.xMin(),
_textBB.yMin() - avg_height * _backdropVerticalOffset,
_textBB.zMin(),
_textBB.xMax(),
_textBB.yMax(),
_textBB.zMax()
);
break;
}
case DROP_SHADOW_TOP_CENTER:
{
_textBB.set(
_textBB.xMin(),
_textBB.yMin(),
_textBB.zMin(),
_textBB.xMax(),
_textBB.yMax() + avg_height * _backdropVerticalOffset,
_textBB.zMax()
);
break;
}
case DROP_SHADOW_BOTTOM_LEFT:
{
_textBB.set(
_textBB.xMin() - avg_width * _backdropHorizontalOffset,
_textBB.yMin() - avg_height * _backdropVerticalOffset,
_textBB.zMin(),
_textBB.xMax(),
_textBB.yMax(),
_textBB.zMax()
);
break;
}
case DROP_SHADOW_CENTER_LEFT:
{
_textBB.set(
_textBB.xMin() - avg_width * _backdropHorizontalOffset,
_textBB.yMin(),
_textBB.zMin(),
_textBB.xMax(),
_textBB.yMax(),
_textBB.zMax()
); break;
}
case DROP_SHADOW_TOP_LEFT:
{
_textBB.set(
_textBB.xMin() - avg_width * _backdropHorizontalOffset,
_textBB.yMin(),
_textBB.zMin(),
_textBB.xMax(),
_textBB.yMax() + avg_height * _backdropVerticalOffset,
_textBB.zMax()
);
break;
}
case OUTLINE:
{
_textBB.set(
_textBB.xMin() - avg_width * _backdropHorizontalOffset,
_textBB.yMin() - avg_height * _backdropVerticalOffset,
_textBB.zMin(),
_textBB.xMax() + avg_width * _backdropHorizontalOffset,
_textBB.yMax() + avg_height * _backdropVerticalOffset,
_textBB.zMax()
);
break;
}
default: // error
{
break;
}
}
}
// This method expands the bounding box to a settable margin when a bounding box drawing mode is active.
void Text::computeBoundingBoxMargin()
{
if(_drawMode & (BOUNDINGBOX | FILLEDBOUNDINGBOX)){
_textBB.set(
_textBB.xMin() - _textBBMargin,
_textBB.yMin() - _textBBMargin,
_textBB.zMin(),
_textBB.xMax() + _textBBMargin,
_textBB.yMax() + _textBBMargin,
_textBB.zMax()
);
}
}
void Text::computeColorGradients()
{
switch(_colorGradientMode)
{
case SOLID:
return;
break;
case PER_CHARACTER:
computeColorGradientsPerCharacter();
break;
case OVERALL:
computeColorGradientsOverall();
break;
default:
break;
}
}
void Text::computeColorGradientsOverall()
{
float min_x = FLT_MAX;
float min_y = FLT_MAX;
float max_x = FLT_MIN;
float max_y = FLT_MIN;
unsigned int i;
const Coords& coords = _coords;
for(i=0;i<coords->size();++i)
{
// Min and Max are needed for color gradients
if((*coords)[i].x() > max_x)
{
max_x = (*coords)[i].x();
}
if ((*coords)[i].x() < min_x)
{
min_x = (*coords)[i].x();
}
if ((*coords)[i].y() > max_y)
{
max_y = (*coords)[i].y();
}
if ((*coords)[i].y() < min_y)
{
min_y = (*coords)[i].y();
}
}
const ColorCoords& colorCoords = _colorCoords;
unsigned int numCoords = coords->size();
if (numCoords!=colorCoords->size())
{
colorCoords->resize(numCoords);
}
for(i=0;i<numCoords;++i)
{
float red = bilinearInterpolate(
min_x,
max_x,
min_y,
max_y,
(*coords)[i].x(),
(*coords)[i].y(),
_colorGradientBottomLeft[0],
_colorGradientTopLeft[0],
_colorGradientBottomRight[0],
_colorGradientTopRight[0]
);
float green = bilinearInterpolate(
min_x,
max_x,
min_y,
max_y,
(*coords)[i].x(),
(*coords)[i].y(),
_colorGradientBottomLeft[1],
_colorGradientTopLeft[1],
_colorGradientBottomRight[1],
_colorGradientTopRight[1]
);
float blue = bilinearInterpolate(
min_x,
max_x,
min_y,
max_y,
(*coords)[i].x(),
(*coords)[i].y(),
_colorGradientBottomLeft[2],
_colorGradientTopLeft[2],
_colorGradientBottomRight[2],
_colorGradientTopRight[2]
);
// Alpha does not convert to HSV
float alpha = bilinearInterpolate(
min_x,
max_x,
min_y,
max_y,
(*coords)[i].x(),
(*coords)[i].y(),
_colorGradientBottomLeft[3],
_colorGradientTopLeft[3],
_colorGradientBottomRight[3],
_colorGradientTopRight[3]
);
(*colorCoords)[i] = osg::Vec4(red,green,blue,alpha);
}
}
void Text::computeColorGradientsPerCharacter()
{
Coords& coords = _coords;
ColorCoords& colorCoords = _colorCoords;
unsigned int numCoords = coords->size();
if (numCoords!=colorCoords->size())
{
colorCoords->resize(numCoords);
}
for(unsigned int i=0;i<numCoords;++i)
{
switch(i%4)
{
case 0: // top-left
{
(*colorCoords)[i] = _colorGradientTopLeft;
break;
}
case 1: // bottom-left
{
(*colorCoords)[i] = _colorGradientBottomLeft;
break;
}
case 2: // bottom-right
{
(*colorCoords)[i] = _colorGradientBottomRight;
break;
}
case 3: // top-right
{
(*colorCoords)[i] = _colorGradientTopRight;
break;
}
default: // error
{
(*colorCoords)[i] = osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
}
}
}
}
void Text::drawImplementation(osg::RenderInfo& renderInfo) const
{
drawImplementation(*renderInfo.getState(), osg::Vec4(1.0f,1.0f,1.0f,1.0f));
}
void Text::drawImplementationSinglePass(osg::State& state, const osg::Vec4& colorMultiplier) const
{
osg::VertexArrayState* vas = state.getCurrentVertexArrayState();
bool usingVertexBufferObjects = state.useVertexBufferObject(_supportsVertexBufferObjects && _useVertexBufferObjects);
bool usingVertexArrayObjects = usingVertexBufferObjects && state.useVertexArrayObject(_useVertexArrayObject);
bool requiresSetArrays = !usingVertexBufferObjects || !usingVertexArrayObjects || vas->getRequiresSetArrays();
if ((_drawMode&(~TEXT))!=0 && !_decorationPrimitives.empty())
{
state.applyTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OFF);
vas->disableColorArray(state);
for(Primitives::const_iterator itr = _decorationPrimitives.begin();
itr != _decorationPrimitives.end();
++itr)
{
if ((*itr)->getMode()==GL_TRIANGLES) state.Color(colorMultiplier.r()*_textBBColor.r(), colorMultiplier.g()*_textBBColor.g(), colorMultiplier.b()*_textBBColor.b(), colorMultiplier.a()*_textBBColor.a());
else state.Color(colorMultiplier.r(), colorMultiplier.g(), colorMultiplier.b(), colorMultiplier.a());
(*itr)->draw(state, usingVertexBufferObjects);
}
state.applyTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::ON);
}
if (_drawMode & TEXT)
{
for(TextureGlyphQuadMap::const_iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
// need to set the texture here...
state.applyTextureAttribute(0,titr->first.get());
const GlyphQuads& glyphquad = titr->second;
if(_colorGradientMode == SOLID)
{
vas->disableColorArray(state);
state.Color(colorMultiplier.r()*_color.r(),colorMultiplier.g()*_color.g(),colorMultiplier.b()*_color.b(),colorMultiplier.a()*_color.a());
}
else
{
if (requiresSetArrays)
{
vas->setColorArray(state, _colorCoords.get());
}
}
glyphquad._primitives->draw(state, usingVertexBufferObjects);
}
}
}
void Text::drawImplementation(osg::State& state, const osg::Vec4& colorMultiplier) const
{
// save the previous modelview matrix
osg::Matrix previous_modelview = state.getModelViewMatrix();
// set up the new modelview matrix
osg::Matrix modelview;
bool needToApplyMatrix = computeMatrix(modelview, &state);
if (needToApplyMatrix)
{
// ** mult previous by the modelview for this context
modelview.postMult(previous_modelview);
// ** apply this new modelview matrix
state.applyModelViewMatrix(modelview);
// workaround for GL3/GL2
if (state.getUseModelViewAndProjectionUniforms()) state.applyModelViewAndProjectionUniformsIfRequired();
// OSG_NOTICE<<"New state.applyModelViewMatrix() "<<modelview<<std::endl;
}
else
{
// OSG_NOTICE<<"No need to apply matrix "<<std::endl;
}
state.Normal(_normal.x(), _normal.y(), _normal.z());
osg::VertexArrayState* vas = state.getCurrentVertexArrayState();
bool usingVertexBufferObjects = state.useVertexBufferObject(_supportsVertexBufferObjects && _useVertexBufferObjects);
bool usingVertexArrayObjects = usingVertexBufferObjects && state.useVertexArrayObject(_useVertexArrayObject);
bool requiresSetArrays = !usingVertexBufferObjects || !usingVertexArrayObjects || vas->getRequiresSetArrays();
if (requiresSetArrays)
{
vas->lazyDisablingOfVertexAttributes();
vas->setVertexArray(state, _coords.get());
vas->setTexCoordArray(state, 0, _texcoords.get());
vas->applyDisablingOfVertexAttributes(state);
}
glDepthMask(GL_FALSE);
drawImplementationSinglePass(state, colorMultiplier);
if (_enableDepthWrites)
{
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_TRUE);
drawImplementationSinglePass(state, colorMultiplier);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
state.haveAppliedAttribute(osg::StateAttribute::COLORMASK);
}
state.haveAppliedAttribute(osg::StateAttribute::DEPTH);
if (usingVertexBufferObjects && !usingVertexArrayObjects)
{
// unbind the VBO's if any are used.
vas->unbindVertexBufferObject();
vas->unbindElementBufferObject();
}
if (needToApplyMatrix)
{
// apply this new modelview matrix
state.applyModelViewMatrix(previous_modelview);
// workaround for GL3/GL2
if (state.getUseModelViewAndProjectionUniforms()) state.applyModelViewAndProjectionUniformsIfRequired();
}
}
void Text::accept(osg::Drawable::ConstAttributeFunctor& af) const
{
if (_coords.valid() )
{
af.apply(osg::Drawable::VERTICES, _coords->size(), &(_coords->front()));
af.apply(osg::Drawable::TEXTURE_COORDS_0, _texcoords->size(), &(_texcoords->front()));
}
}
void Text::accept(osg::PrimitiveFunctor& pf) const
{
pf.setVertexArray(_coords->size(), &(_coords->front()));
for(TextureGlyphQuadMap::const_iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
const GlyphQuads& glyphquad = titr->second;
if (glyphquad._primitives.valid())
{
const osg::DrawElementsUShort* drawElementsUShort = dynamic_cast<const osg::DrawElementsUShort*>(glyphquad._primitives.get());
if (drawElementsUShort)
{
pf.drawElements(GL_TRIANGLES, drawElementsUShort->size(), &(drawElementsUShort->front()));
}
else
{
const osg::DrawElementsUInt* drawElementsUInt = dynamic_cast<const osg::DrawElementsUInt*>(glyphquad._primitives.get());
if (drawElementsUInt)
{
pf.drawElements(GL_TRIANGLES, drawElementsUInt->size(), &(drawElementsUInt->front()));
}
}
}
}
}
void Text::resizeGLObjectBuffers(unsigned int maxSize)
{
TextBase::resizeGLObjectBuffers(maxSize);
for(TextureGlyphQuadMap::iterator itr = _textureGlyphQuadMap.begin();
itr != _textureGlyphQuadMap.end();
++itr)
{
itr->second.resizeGLObjectBuffers(maxSize);
}
}
void Text::releaseGLObjects(osg::State* state) const
{
TextBase::releaseGLObjects(state);
for(TextureGlyphQuadMap::const_iterator itr = _textureGlyphQuadMap.begin();
itr != _textureGlyphQuadMap.end();
++itr)
{
itr->second.releaseGLObjects(state);
}
}
void Text::setBackdropType(BackdropType type)
{
if (_backdropType==type) return;
_backdropType = type;
assignStateSet();
computeGlyphRepresentation();
}
void Text::setBackdropOffset(float offset)
{
_backdropHorizontalOffset = offset;
_backdropVerticalOffset = offset;
assignStateSet();
computeGlyphRepresentation();
}
void Text::setBackdropOffset(float horizontal, float vertical)
{
_backdropHorizontalOffset = horizontal;
_backdropVerticalOffset = vertical;
assignStateSet();
computeGlyphRepresentation();
}
void Text::setBackdropColor(const osg::Vec4& color)
{
_backdropColor = color;
assignStateSet();
}
void Text::setColorGradientMode(ColorGradientMode mode)
{
if (_colorGradientMode==mode) return;
_colorGradientMode = mode;
computeGlyphRepresentation();
}
void Text::setColorGradientCorners(const osg::Vec4& topLeft, const osg::Vec4& bottomLeft, const osg::Vec4& bottomRight, const osg::Vec4& topRight)
{
_colorGradientTopLeft = topLeft;
_colorGradientBottomLeft = bottomLeft;
_colorGradientBottomRight = bottomRight;
_colorGradientTopRight = topRight;
computeGlyphRepresentation();
}
// Formula for f(x,y) from Wikipedia "Bilinear interpolation", 2006-06-18
float Text::bilinearInterpolate(float x1, float x2, float y1, float y2, float x, float y, float q11, float q12, float q21, float q22) const
{
return (
((q11 / ((x2-x1)*(y2-y1))) * (x2-x)*(y2-y))
+ ((q21 / ((x2-x1)*(y2-y1))) * (x-x1)*(y2-y))
+ ((q12 / ((x2-x1)*(y2-y1))) * (x2-x)*(y-y1))
+ ((q22 / ((x2-x1)*(y2-y1))) * (x-x1)*(y-y1))
);
}
Text::GlyphQuads::GlyphQuads()
{
}
Text::GlyphQuads::GlyphQuads(const GlyphQuads&)
{
}
void Text::GlyphQuads::resizeGLObjectBuffers(unsigned int maxSize)
{
if (_primitives.valid()) _primitives->resizeGLObjectBuffers(maxSize);
}
void Text::GlyphQuads::releaseGLObjects(osg::State* state) const
{
if (_primitives.valid()) _primitives->releaseGLObjects(state);
}