Files
OpenSceneGraph/src/osgText/Text.cpp
2017-02-24 11:17:22 +00:00

1932 lines
63 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>
using namespace osg;
using namespace osgText;
//#define TREES_CODE_FOR_MAKING_SPACES_EDITABLE
Text::Text():
_enableDepthWrites(true),
_backdropType(NONE),
_backdropImplementation(DELAYED_DEPTH_WRITES),
_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;
}
Text::Text(const Text& text,const osg::CopyOp& copyop):
osgText::TextBase(text,copyop),
_enableDepthWrites(text._enableDepthWrites),
_backdropType(text._backdropType),
_backdropImplementation(text._backdropImplementation),
_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::setFont(osg::ref_ptr<Font> font)
{
if (_font==font) return;
osg::StateSet* previousFontStateSet = _font.valid() ? _font->getStateSet() : Font::getDefaultFont()->getStateSet();
osg::StateSet* newFontStateSet = font.valid() ? font->getStateSet() : Font::getDefaultFont()->getStateSet();
if (getStateSet() == previousFontStateSet)
{
setStateSet( newFontStateSet );
}
TextBase::setFont(font);
}
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::computeGlyphRepresentation()
{
Font* activefont = getActiveFont();
if (!activefont) return;
_textureGlyphQuadMap.clear();
_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;
// Adjust coordinates and texture coordinates to avoid
// clipping the edges of antialiased characters.
osg::Vec2 mintc = glyph->getMinTexCoord();
osg::Vec2 maxtc = glyph->getMaxTexCoord();
osg::Vec2 vDiff = maxtc - mintc;
float fHorizTCMargin = 1.0f / glyph->getTexture()->getTextureWidth();
float fVertTCMargin = 1.0f / glyph->getTexture()->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;
// set up the coords of the quad
osg::Vec2 upLeft = local+osg::Vec2(0.0f-fHorizQuadMargin,height+fVertQuadMargin);
osg::Vec2 lowLeft = local+osg::Vec2(0.0f-fHorizQuadMargin,0.0f-fVertQuadMargin);
osg::Vec2 lowRight = local+osg::Vec2(width+fHorizQuadMargin,0.0f-fVertQuadMargin);
osg::Vec2 upRight = local+osg::Vec2(width+fHorizQuadMargin,height+fVertQuadMargin);
GlyphQuads& glyphquad = _textureGlyphQuadMap[glyph->getTexture()];
glyphquad._glyphs.push_back(glyph);
glyphquad.addCoord(upLeft);
glyphquad.addCoord(lowLeft);
glyphquad.addCoord(lowRight);
glyphquad.addCoord(upRight);
// set up the tex coords of the quad
glyphquad.addTexCoord(osg::Vec2(mintc.x(), maxtc.y()));
glyphquad.addTexCoord(osg::Vec2(mintc.x(), mintc.y()));
glyphquad.addTexCoord(osg::Vec2(maxtc.x(), mintc.y()));
glyphquad.addTexCoord(osg::Vec2(maxtc.x(), maxtc.y()));
// 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(lowLeft.x(), lowLeft.y(), 0.0f)); //lower left corner
_textBB.expandBy(osg::Vec3(upRight.x(), upRight.y(), 0.0f)); //upper right corner
break;
case VERTICAL:
cursor.y() -= glyph->getVerticalAdvance() * hr;
_textBB.expandBy(osg::Vec3(upLeft.x(),upLeft.y(),0.0f)); //upper left corner
_textBB.expandBy(osg::Vec3(lowRight.x(),lowRight.y(),0.0f)); //lower right corner
break;
case RIGHT_TO_LEFT:
_textBB.expandBy(osg::Vec3(lowRight.x(),lowRight.y(),0.0f)); //lower right corner
_textBB.expandBy(osg::Vec3(upLeft.x(),upLeft.y(),0.0f)); //upper left corner
break;
}
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;
}
}
for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
titr->second.updatePrimitives();
if (_useVertexBufferObjects)
{
titr->second.initGPUBufferObjects();
}
}
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;
// This section is going to try to compute the average width and height
// for a character among the text. The reason I shift by an
// average amount per-character instead of shifting each character
// by its per-instance amount is because it may look strange to see
// the individual backdrop text letters not space themselves the same
// way the foreground text does. Using one value gives uniformity.
// Note: This loop is repeated for each context. I think it may produce
// the same values regardless of context. This code be optimized by moving
// this loop outside the loop.
for(TextureGlyphQuadMap::const_iterator const_titr=_textureGlyphQuadMap.begin();
const_titr!=_textureGlyphQuadMap.end();
++const_titr)
{
const GlyphQuads& glyphquad = const_titr->second;
const GlyphQuads::Coords& coords = glyphquad._coords;
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();
computeBackdropPositions();
computeBackdropBoundingBox();
computeBoundingBoxMargin();
}
// Presumes the atc matrix is already up-to-date
void Text::computeBackdropPositions()
{
if(_backdropType == NONE)
{
return;
}
float avg_width = 0.0f;
float avg_height = 0.0f;
unsigned int i;
bool is_valid_size;
// FIXME: OPTIMIZE: This function produces the same value regardless of contextID.
// Since we tend to loop over contextID, we should cache this value some how
// instead of recomputing it each time.
is_valid_size = computeAverageGlyphWidthAndHeight(avg_width, avg_height);
if (!is_valid_size) return;
// now apply matrix to the glyphs.
for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
GlyphQuads& glyphquad = titr->second;
GlyphQuads::Coords& coords = glyphquad._coords;
unsigned int backdrop_index;
unsigned int max_backdrop_index;
if(_backdropType == OUTLINE)
{
// For outline, we want to draw the in every direction
backdrop_index = 0;
max_backdrop_index = 8;
}
else
{
// Yes, this may seem a little strange,
// but since the code is using references,
// I would have to duplicate the following code twice
// for each part of the if/else because I can't
// declare a reference without setting it immediately
// and it wouldn't survive the scope.
// So it happens that the _backdropType value matches
// the index in the array I want to store the coordinates
// in. So I'll just setup the for-loop so it only does
// the one direction I'm interested in.
backdrop_index = _backdropType;
max_backdrop_index = _backdropType+1;
}
for( ; backdrop_index < max_backdrop_index; backdrop_index++)
{
GlyphQuads::Coords& transformedCoords = glyphquad._transformedBackdropCoords[backdrop_index];
if (!transformedCoords) transformedCoords = new osg::Vec3Array();
unsigned int numCoords = coords->size();
if (numCoords!=transformedCoords->size())
{
transformedCoords->resize(numCoords);
}
for(i=0;i<numCoords;++i)
{
float horizontal_shift_direction;
float vertical_shift_direction;
switch(backdrop_index)
{
case DROP_SHADOW_BOTTOM_RIGHT:
{
horizontal_shift_direction = 1.0f;
vertical_shift_direction = -1.0f;
break;
}
case DROP_SHADOW_CENTER_RIGHT:
{
horizontal_shift_direction = 1.0f;
vertical_shift_direction = 0.0f;
break;
}
case DROP_SHADOW_TOP_RIGHT:
{
horizontal_shift_direction = 1.0f;
vertical_shift_direction = 1.0f;
break;
}
case DROP_SHADOW_BOTTOM_CENTER:
{
horizontal_shift_direction = 0.0f;
vertical_shift_direction = -1.0f;
break;
}
case DROP_SHADOW_TOP_CENTER:
{
horizontal_shift_direction = 0.0f;
vertical_shift_direction = 1.0f;
break;
}
case DROP_SHADOW_BOTTOM_LEFT:
{
horizontal_shift_direction = -1.0f;
vertical_shift_direction = -1.0f;
break;
}
case DROP_SHADOW_CENTER_LEFT:
{
horizontal_shift_direction = -1.0f;
vertical_shift_direction = 0.0f;
break;
}
case DROP_SHADOW_TOP_LEFT:
{
horizontal_shift_direction = -1.0f;
vertical_shift_direction = 1.0f;
break;
}
default: // error
{
horizontal_shift_direction = 1.0f;
vertical_shift_direction = -1.0f;
}
}
(*transformedCoords)[i] = osg::Vec3(horizontal_shift_direction * _backdropHorizontalOffset * avg_width + (*coords)[i].x(), vertical_shift_direction * _backdropVerticalOffset * avg_height + (*coords)[i].y(), 0.0f);
transformedCoords->dirty();
}
}
}
}
// 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;
for(TextureGlyphQuadMap::const_iterator const_titr=_textureGlyphQuadMap.begin();
const_titr!=_textureGlyphQuadMap.end();
++const_titr)
{
const GlyphQuads& glyphquad = const_titr->second;
const GlyphQuads::Coords& coords = glyphquad._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();
}
}
}
for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
GlyphQuads& glyphquad = titr->second;
GlyphQuads::Coords& coords = glyphquad._coords;
GlyphQuads::ColorCoords& colorCoords = glyphquad._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()
{
for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
GlyphQuads& glyphquad = titr->second;
GlyphQuads::Coords& coords = glyphquad._coords;
GlyphQuads::ColorCoords& colorCoords = glyphquad._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::drawImplementation(osg::State& state, const osg::Vec4& colorMultiplier) const
{
state.applyMode(GL_BLEND,true);
#if defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
state.applyTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::ON);
state.applyTextureAttribute(0,getActiveFont()->getTexEnv());
#endif
// 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);
// 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());
if ((_drawMode&(~TEXT))!=0)
{
state.disableNormalPointer();
state.Color(colorMultiplier.r()*_textBBColor.r(),colorMultiplier.g()*_textBBColor.g(),colorMultiplier.b()*_textBBColor.b(),colorMultiplier.a()*_textBBColor.a());
if (_decorationVertices.valid() && !_decorationVertices->empty())
{
osg::State::ApplyModeProxy applyMode(state, GL_LIGHTING, false);
osg::State::ApplyTextureModeProxy applyTextureMode(state, 0, GL_TEXTURE_2D, false);
state.setVertexPointer(_decorationVertices.get());
unsigned int start_index = 0;
if ((_drawMode & FILLEDBOUNDINGBOX)!=0 && _textBB.valid())
{
#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GL3_AVAILABLE)
switch(_backdropImplementation)
{
case NO_DEPTH_BUFFER:
// Do nothing. The bounding box will be rendered before the text and that's all that matters.
break;
case DEPTH_RANGE:
glPushAttrib(GL_DEPTH_BUFFER_BIT);
//unsigned int backdrop_index = 0;
//unsigned int max_backdrop_index = 8;
//const double offset = double(max_backdrop_index - backdrop_index) * 0.003;
glDepthRange(0.001, 1.001);
break;
/*case STENCIL_BUFFER:
break;*/
default:
glPushAttrib(GL_POLYGON_OFFSET_FILL);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0.1f * osg::PolygonOffset::getFactorMultiplier(), 10.0f * osg::PolygonOffset::getUnitsMultiplier() );
}
glDrawArrays(GL_QUADS, 0, 4);
start_index += 4;
switch(_backdropImplementation)
{
case NO_DEPTH_BUFFER:
// Do nothing.
break;
case DEPTH_RANGE:
glDepthRange(0.0, 1.0);
glPopAttrib();
break;
/*case STENCIL_BUFFER:
break;*/
default:
glDisable(GL_POLYGON_OFFSET_FILL);
glPopAttrib();
}
#endif
}
if (start_index<_decorationVertices->size())
{
state.Color(colorMultiplier.r(),colorMultiplier.g(),colorMultiplier.b(),colorMultiplier.a());
glDrawArrays(GL_LINES, start_index, _decorationVertices->size());
}
}
}
#if defined(OSG_GL_FIXED_FUNCTION_AVAILABLE)
state.applyTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::ON);
state.applyTextureAttribute(0,getActiveFont()->getTexEnv());
#endif
if (_drawMode & TEXT)
{
state.disableAllVertexArrays();
// Okay, since ATI's cards/drivers are not working correctly,
// we need alternative solutions to glPolygonOffset.
// So this is a pick your poison approach. Each alternative
// backend has trade-offs associated with it, but with luck,
// the user may find that works for them.
if(_backdropType != NONE && _backdropImplementation != DELAYED_DEPTH_WRITES)
{
switch(_backdropImplementation)
{
case POLYGON_OFFSET:
renderWithPolygonOffset(state,colorMultiplier);
break;
case NO_DEPTH_BUFFER:
renderWithNoDepthBuffer(state,colorMultiplier);
break;
case DEPTH_RANGE:
renderWithDepthRange(state,colorMultiplier);
break;
case STENCIL_BUFFER:
renderWithStencilBuffer(state,colorMultiplier);
break;
default:
renderWithPolygonOffset(state,colorMultiplier);
}
}
else
{
renderWithDelayedDepthWrites(state,colorMultiplier);
}
// unbind buffers if necessary
state.unbindVertexBufferObject();
state.unbindElementBufferObject();
}
if (needToApplyMatrix)
{
// apply this new modelview matrix
state.applyModelViewMatrix(previous_modelview);
}
}
void Text::accept(osg::Drawable::ConstAttributeFunctor& af) const
{
// TODO what to do about local transforms?
for(TextureGlyphQuadMap::const_iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
const GlyphQuads& glyphquad = titr->second;
if (glyphquad._coords.valid() )
{
af.apply(osg::Drawable::VERTICES, glyphquad._coords->size(), &(glyphquad._coords->front()));
af.apply(osg::Drawable::TEXTURE_COORDS_0, glyphquad._texcoords->size(), &(glyphquad._texcoords->front()));
}
}
}
void Text::accept(osg::PrimitiveFunctor& pf) const
{
// TODO what to do about local transforms?
for(TextureGlyphQuadMap::const_iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
const GlyphQuads& glyphquad = titr->second;
if (glyphquad._coords.valid())
{
pf.setVertexArray(glyphquad._coords->size(), &(glyphquad._coords->front()));
pf.drawArrays(GL_QUADS, 0, glyphquad._coords->size());
}
}
}
void Text::setThreadSafeRefUnref(bool threadSafe)
{
TextBase::setThreadSafeRefUnref(threadSafe);
getActiveFont()->setThreadSafeRefUnref(threadSafe);
}
void Text::resizeGLObjectBuffers(unsigned int maxSize)
{
TextBase::resizeGLObjectBuffers(maxSize);
getActiveFont()->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);
getActiveFont()->releaseGLObjects(state);
for(TextureGlyphQuadMap::iterator itr = _textureGlyphQuadMap.begin();
itr != _textureGlyphQuadMap.end();
++itr)
{
itr->second.releaseGLObjects(state);
}
}
void Text::setBackdropType(BackdropType type)
{
if (_backdropType==type) return;
_backdropType = type;
computeGlyphRepresentation();
}
void Text::setBackdropImplementation(BackdropImplementation implementation)
{
if (_backdropImplementation==implementation) return;
_backdropImplementation = implementation;
computeGlyphRepresentation();
}
void Text::setBackdropOffset(float offset)
{
_backdropHorizontalOffset = offset;
_backdropVerticalOffset = offset;
computeGlyphRepresentation();
}
void Text::setBackdropOffset(float horizontal, float vertical)
{
_backdropHorizontalOffset = horizontal;
_backdropVerticalOffset = vertical;
computeGlyphRepresentation();
}
void Text::setBackdropColor(const osg::Vec4& color)
{
_backdropColor = color;
}
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))
);
}
void Text::drawForegroundText(osg::State& state, const GlyphQuads& glyphquad, const osg::Vec4& colorMultiplier) const
{
const GlyphQuads::Coords& transformedCoords = glyphquad._coords;
if (transformedCoords.valid() && !transformedCoords->empty())
{
state.setVertexPointer(transformedCoords.get());
state.setTexCoordPointer(0, glyphquad._texcoords.get());
if(_colorGradientMode == SOLID)
{
state.disableColorPointer();
state.Color(colorMultiplier.r()*_color.r(),colorMultiplier.g()*_color.g(),colorMultiplier.b()*_color.b(),colorMultiplier.a()*_color.a());
}
else
{
state.setColorPointer(glyphquad._colorCoords.get());
}
glyphquad._primitives[0]->draw(state, _useVertexBufferObjects);
}
}
void Text::renderOnlyForegroundText(osg::State& state, const osg::Vec4& colorMultiplier) const
{
for(TextureGlyphQuadMap::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;
drawForegroundText(state, glyphquad, colorMultiplier);
}
}
void Text::renderWithDelayedDepthWrites(osg::State& state, const osg::Vec4& colorMultiplier) const
{
// If depth testing is disabled, then just render text as normal
if( !state.getLastAppliedMode(GL_DEPTH_TEST) ) {
drawTextWithBackdrop(state,colorMultiplier);
return;
}
//glPushAttrib( _enableDepthWrites ? (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) : GL_DEPTH_BUFFER_BIT);
// Render to color buffer without writing to depth buffer.
glDepthMask(GL_FALSE);
drawTextWithBackdrop(state,colorMultiplier);
// Render to depth buffer if depth writes requested.
if( _enableDepthWrites )
{
glDepthMask(GL_TRUE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
drawTextWithBackdrop(state,colorMultiplier);
}
state.haveAppliedAttribute(osg::StateAttribute::DEPTH);
state.haveAppliedAttribute(osg::StateAttribute::COLORMASK);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
//glPopAttrib();
}
void Text::drawTextWithBackdrop(osg::State& state, const osg::Vec4& colorMultiplier) const
{
for(TextureGlyphQuadMap::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(_backdropType != NONE)
{
unsigned int backdrop_index;
unsigned int max_backdrop_index;
if(_backdropType == OUTLINE)
{
backdrop_index = 0;
max_backdrop_index = 8;
}
else
{
backdrop_index = _backdropType;
max_backdrop_index = _backdropType+1;
}
state.setTexCoordPointer(0, glyphquad._texcoords.get());
state.disableColorPointer();
state.Color(_backdropColor.r(),_backdropColor.g(),_backdropColor.b(),_backdropColor.a());
for( ; backdrop_index < max_backdrop_index; backdrop_index++)
{
const GlyphQuads::Coords& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index];
if (transformedBackdropCoords.valid() && !transformedBackdropCoords->empty())
{
state.setVertexPointer(transformedBackdropCoords.get());
glyphquad._primitives[backdrop_index+1]->draw(state, _useVertexBufferObjects);
}
}
}
drawForegroundText(state, glyphquad, colorMultiplier);
}
}
void Text::renderWithPolygonOffset(osg::State& state, const osg::Vec4& colorMultiplier) const
{
#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GL3_AVAILABLE)
if (!osg::PolygonOffset::areFactorAndUnitsMultipliersSet())
{
osg::PolygonOffset::setFactorAndUnitsMultipliersUsingBestGuessForDriver();
}
// Do I really need to do this for glPolygonOffset?
glPushAttrib(GL_POLYGON_OFFSET_FILL);
glEnable(GL_POLYGON_OFFSET_FILL);
for(TextureGlyphQuadMap::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;
unsigned int backdrop_index;
unsigned int max_backdrop_index;
if(_backdropType == OUTLINE)
{
backdrop_index = 0;
max_backdrop_index = 8;
}
else
{
backdrop_index = _backdropType;
max_backdrop_index = _backdropType+1;
}
state.setTexCoordPointer( 0, glyphquad._texcoords.get());
state.disableColorPointer();
state.Color(_backdropColor.r(),_backdropColor.g(),_backdropColor.b(),_backdropColor.a());
for( ; backdrop_index < max_backdrop_index; backdrop_index++)
{
const GlyphQuads::Coords& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index];
if (transformedBackdropCoords.valid() && !transformedBackdropCoords->empty())
{
glPolygonOffset(0.1f * osg::PolygonOffset::getFactorMultiplier(), osg::PolygonOffset::getUnitsMultiplier() * (max_backdrop_index-backdrop_index) );
state.setVertexPointer( transformedBackdropCoords.get());
glyphquad._primitives[backdrop_index+1]->draw(state, _useVertexBufferObjects);
}
}
// Reset the polygon offset so the foreground text is on top
glPolygonOffset(0.0f,0.0f);
drawForegroundText(state, glyphquad, colorMultiplier);
}
glPopAttrib();
#else
OSG_NOTICE<<"Warning: Text::renderWithPolygonOffset(..) not implemented."<<std::endl;
#endif
}
void Text::renderWithNoDepthBuffer(osg::State& state, const osg::Vec4& colorMultiplier) const
{
#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GL3_AVAILABLE)
glPushAttrib(GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
for(TextureGlyphQuadMap::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;
unsigned int backdrop_index;
unsigned int max_backdrop_index;
if(_backdropType == OUTLINE)
{
backdrop_index = 0;
max_backdrop_index = 8;
}
else
{
backdrop_index = _backdropType;
max_backdrop_index = _backdropType+1;
}
state.setTexCoordPointer( 0, glyphquad._texcoords.get());
state.disableColorPointer();
state.Color(_backdropColor.r(),_backdropColor.g(),_backdropColor.b(),_backdropColor.a());
for( ; backdrop_index < max_backdrop_index; backdrop_index++)
{
const GlyphQuads::Coords& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index];
if (transformedBackdropCoords.valid() && !transformedBackdropCoords->empty())
{
state.setVertexPointer( transformedBackdropCoords.get());
glyphquad._primitives[backdrop_index+1]->draw(state, _useVertexBufferObjects);
}
}
drawForegroundText(state, glyphquad, colorMultiplier);
}
glPopAttrib();
#else
OSG_NOTICE<<"Warning: Text::renderWithNoDepthBuffer(..) not implemented."<<std::endl;
#endif
}
// This idea comes from Paul Martz's OpenGL FAQ: 13.050
void Text::renderWithDepthRange(osg::State& state, const osg::Vec4& colorMultiplier) const
{
#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GL3_AVAILABLE)
// Hmmm, the man page says GL_VIEWPORT_BIT for Depth range (near and far)
// but experimentally, GL_DEPTH_BUFFER_BIT for glDepthRange.
// glPushAttrib(GL_VIEWPORT_BIT);
glPushAttrib(GL_DEPTH_BUFFER_BIT);
for(TextureGlyphQuadMap::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;
unsigned int backdrop_index;
unsigned int max_backdrop_index;
if(_backdropType == OUTLINE)
{
backdrop_index = 0;
max_backdrop_index = 8;
}
else
{
backdrop_index = _backdropType;
max_backdrop_index = _backdropType+1;
}
state.setTexCoordPointer( 0, glyphquad._texcoords.get());
state.disableColorPointer();
state.Color(_backdropColor.r(),_backdropColor.g(),_backdropColor.b(),_backdropColor.a());
for( ; backdrop_index < max_backdrop_index; backdrop_index++)
{
const GlyphQuads::Coords& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index];
if (transformedBackdropCoords.valid() && !transformedBackdropCoords->empty())
{
double offset = double(max_backdrop_index-backdrop_index)*0.0001;
glDepthRange( offset, 1.0+offset);
state.setVertexPointer( transformedBackdropCoords.get());
state.drawQuads(0,transformedBackdropCoords->size());
}
}
glDepthRange(0.0, 1.0);
drawForegroundText(state, glyphquad, colorMultiplier);
}
glPopAttrib();
#else
OSG_NOTICE<<"Warning: Text::renderWithDepthRange(..) not implemented."<<std::endl;
#endif
}
void Text::renderWithStencilBuffer(osg::State& state, const osg::Vec4& colorMultiplier) const
{
#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GL3_AVAILABLE)
/* Here are the steps:
* 1) Disable drawing color
* 2) Enable the stencil buffer
* 3) Draw all the text to the stencil buffer
* 4) Disable the stencil buffer
* 5) Enable color
* 6) Disable the depth buffer
* 7) Draw all the text again.
* 7b) Make sure the foreground text is drawn last if priority levels
* are the same OR
* 7c) If priority levels are different, then make sure the foreground
* text has the higher priority.
*/
TextureGlyphQuadMap::iterator titr; // Moved up here for VC6
glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_STENCIL_TEST);
// It seems I can get away without calling this here
//glClear(GL_STENCIL_BUFFER_BIT);
// enable stencil buffer
glEnable(GL_STENCIL_TEST);
// write a one to the stencil buffer everywhere we are about to draw
glStencilFunc(GL_ALWAYS, 1, 1);
// write only to the stencil buffer if we pass the depth test
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
// Disable writing to the color buffer so we only write to the stencil
// buffer and the depth buffer
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
// make sure the depth buffer is enabled
// glEnable(GL_DEPTH_TEST);
// glDepthMask(GL_TRUE);
// glDepthFunc(GL_LESS);
// Arrrgh! Why does the code only seem to work correctly if I call this?
glDepthMask(GL_FALSE);
// Draw all the text to the stencil buffer to mark out the region
// that we can write too.
for(titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
// need to set the texture here...
state.applyTextureAttribute(0,titr->first.get());
const GlyphQuads& glyphquad = titr->second;
unsigned int backdrop_index;
unsigned int max_backdrop_index;
if(_backdropType == OUTLINE)
{
backdrop_index = 0;
max_backdrop_index = 8;
}
else
{
backdrop_index = _backdropType;
max_backdrop_index = _backdropType+1;
}
state.setTexCoordPointer( 0, glyphquad._texcoords.get());
state.disableColorPointer();
for( ; backdrop_index < max_backdrop_index; backdrop_index++)
{
const GlyphQuads::Coords& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index];
if (transformedBackdropCoords.valid() && !transformedBackdropCoords->empty())
{
state.setVertexPointer( transformedBackdropCoords.get());
state.drawQuads(0,transformedBackdropCoords->size());
}
}
// Draw the foreground text
const GlyphQuads::Coords& transformedCoords = glyphquad._coords;
if (transformedCoords.valid() && !transformedCoords->empty())
{
state.setVertexPointer( transformedCoords.get());
state.setTexCoordPointer( 0, glyphquad._texcoords.get());
state.drawQuads(0, transformedCoords->size());
}
}
// disable the depth buffer
// glDisable(GL_DEPTH_TEST);
// glDepthMask(GL_FALSE);
// glDepthMask(GL_TRUE);
// glDepthFunc(GL_ALWAYS);
// Set the stencil function to pass when the stencil is 1
// Bug: This call seems to have no effect. Try changing to NOTEQUAL
// and see the exact same results.
glStencilFunc(GL_EQUAL, 1, 1);
// disable writing to the stencil buffer
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilMask(GL_FALSE);
// Re-enable writing to the color buffer so we can see the results
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
// Draw all the text again
for(titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
// need to set the texture here...
state.applyTextureAttribute(0,titr->first.get());
const GlyphQuads& glyphquad = titr->second;
unsigned int backdrop_index;
unsigned int max_backdrop_index;
if(_backdropType == OUTLINE)
{
backdrop_index = 0;
max_backdrop_index = 8;
}
else
{
backdrop_index = _backdropType;
max_backdrop_index = _backdropType+1;
}
state.setTexCoordPointer( 0, glyphquad._texcoords.get());
state.disableColorPointer();
state.Color(_backdropColor.r(),_backdropColor.g(),_backdropColor.b(),_backdropColor.a());
for( ; backdrop_index < max_backdrop_index; backdrop_index++)
{
const GlyphQuads::Coords& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index];
if (transformedBackdropCoords.valid() && !transformedBackdropCoords->empty())
{
state.setVertexPointer( transformedBackdropCoords.get());
state.drawQuads(0,transformedBackdropCoords->size());
}
}
drawForegroundText(state, glyphquad, colorMultiplier);
}
glPopAttrib();
#else
OSG_NOTICE<<"Warning: Text::renderWithStencilBuffer(..) not implemented."<<std::endl;
#endif
}
Text::GlyphQuads::GlyphQuads()
{
initGlyphQuads();
}
Text::GlyphQuads::GlyphQuads(const GlyphQuads&)
{
initGlyphQuads();
}
void Text::GlyphQuads::initGlyphQuads()
{
_coords = new osg::Vec3Array();
_texcoords = new osg::Vec2Array();
_colorCoords = new osg::Vec4Array();
_primitives.push_back(new DrawElementsUShort(PrimitiveSet::TRIANGLES));
for (int j = 0; j < 8; j++)
{
_transformedBackdropCoords[j] = new osg::Vec3Array();
_primitives.push_back(new DrawElementsUShort(PrimitiveSet::TRIANGLES));
}
}
void Text::GlyphQuads::updatePrimitives()
{
for(Primitives::iterator itr = _primitives.begin();
itr != _primitives.end();
++itr)
{
DrawElementsUShort* indices = itr->get();
indices->clear();
for (unsigned short i = 0; i < (unsigned short)_coords->size(); i += 4)
{
indices->push_back(i);
indices->push_back(i + 1);
indices->push_back(i + 3);
indices->push_back(i + 1);
indices->push_back(i + 2);
indices->push_back(i + 3);
}
}
}
void Text::GlyphQuads::initGPUBufferObjects()
{
osg::VertexBufferObject* vbo = new osg::VertexBufferObject();
_coords->setBinding(osg::Array::BIND_PER_VERTEX);
_coords->setVertexBufferObject(vbo);
_texcoords->setBinding(osg::Array::BIND_PER_VERTEX);
_texcoords->setVertexBufferObject(vbo);
_colorCoords->setBinding(osg::Array::BIND_PER_VERTEX);
_colorCoords->setVertexBufferObject(vbo);
for (int j = 0; j < 8; j++)
{
if (_transformedBackdropCoords[j].valid())
{
_transformedBackdropCoords[j]->setBinding(osg::Array::BIND_PER_VERTEX);
_transformedBackdropCoords[j]->setVertexBufferObject(vbo);
}
}
osg::ref_ptr<osg::ElementBufferObject> ebo = new osg::ElementBufferObject();
for(Primitives::iterator itr = _primitives.begin();
itr != _primitives.end();
++itr)
{
(*itr)->setElementBufferObject(ebo.get());
}
}
void Text::GlyphQuads::resizeGLObjectBuffers(unsigned int maxSize)
{
_coords->resizeGLObjectBuffers(maxSize);
_texcoords->resizeGLObjectBuffers(maxSize);
_colorCoords->resizeGLObjectBuffers(maxSize);
for (int j = 0; j < 8; j++)
{
if (_transformedBackdropCoords[j].valid())
{
_transformedBackdropCoords[j]->resizeGLObjectBuffers(maxSize);
}
}
for(Primitives::iterator itr = _primitives.begin();
itr != _primitives.end();
++itr)
{
(*itr)->resizeGLObjectBuffers(maxSize);
}
initGPUBufferObjects();
}
void Text::GlyphQuads::releaseGLObjects(osg::State* state) const
{
_coords->releaseGLObjects(state);;
_texcoords->releaseGLObjects(state);
_colorCoords->releaseGLObjects(state);
for (int j = 0; j < 8; j++)
{
if (_transformedBackdropCoords[j].valid())
{
_transformedBackdropCoords[j]->releaseGLObjects(state);
}
}
for(Primitives::const_iterator itr = _primitives.begin();
itr != _primitives.end();
++itr)
{
(*itr)->releaseGLObjects(state);
}
}