From Eric Wing, added support for outline/shadow and colour gradient effects.

This commit is contained in:
Robert Osfield 2006-06-27 13:09:00 +00:00
parent c71fc9fcb6
commit 710adfd698
2 changed files with 956 additions and 19 deletions

View File

@ -211,13 +211,124 @@ public:
unsigned int getDrawMode() const { return _drawMode; } unsigned int getDrawMode() const { return _drawMode; }
enum BackdropType
{
DROP_SHADOW_BOTTOM_RIGHT = 0, // usually the type of shadow you see
DROP_SHADOW_CENTER_RIGHT,
DROP_SHADOW_TOP_RIGHT,
DROP_SHADOW_BOTTOM_CENTER,
DROP_SHADOW_TOP_CENTER,
DROP_SHADOW_BOTTOM_LEFT,
DROP_SHADOW_CENTER_LEFT,
DROP_SHADOW_TOP_LEFT,
OUTLINE,
NONE
};
/**
* BackdropType gives you a background shadow text behind your regular
* text. This helps give text extra contrast which can be useful when
* placing text against noisy backgrounds.
* The color of the background shadow text is specified by setBackdropColor().
* DROP_SHADOW_BOTTOM_RIGHT will draw backdrop text to the right and down of
* the normal text. Other DROW_SHADOW_* modes do the same for their repective directions.
* OUTLINE will draw backdrop text so that it appears the text has an outline
* or border around the normal text. This mode is particularly useful against
* really noisy backgrounds that may put text on top of things that have
* all types of colors which you don't have control over.
* Some real world examples of this general technique in use that I know of
* are Google Earth, Sid Meier's Pirates (2004 Remake), and Star Control 2 (PC 1993).
* The default is NONE.
*/
void setBackdropType(BackdropType type);
BackdropType getBackdropType() const { return _backdropType; }
/**
* Sets the amount text is offset to create the backdrop/shadow effect.
* Set the value too high and for example, in OUTLINE mode you will get a "Brady Bunch"
* effect where you see duplicates of the text in a 3x3 grid.
* Set the value too small and you won't see anything.
* The values represent percentages. 1.0 means 100% so a value of 1.0
* in DROW_SHADOW_LEFT_CENTER mode would cause each glyph to be echoed
* next to it self. So the letter 'e' might look like 'ee'.
* Good values tend to be in the 0.03 to 0.10 range (but will be subject
* to your specific font and display characteristics).
* Note that the text bounding boxes are updated to include backdrop offsets.
* However, other metric information such as getCharacterHeight() are unaffected
* by this. This means that individual glyph spacing (kerning?) are unchanged
* even when this mode is used.
* The default is 0.07 (7% offset).
*/
void setBackdropOffset(float offset = 0.07f);
/**
* This overloaded version lets you specify the offset for the horizontal
* and vertical components separately.
*/
void setBackdropOffset(float horizontal, float vertical);
float getBackdropHorizontalOffet() const { return _backdropHorizontalOffset; }
float getBackdropVerticalOffset() const { return _backdropVerticalOffset; }
/**
* This specifies the color of the backdrop text.
* The default is black.
*/
void setBackdropColor(const osg::Vec4& color);
const osg::Vec4& getBackdropColor() const { return _backdropColor; }
enum ColorGradientMode
{
SOLID = 0, // a.k.a. ColorGradients off
PER_CHARACTER,
OVERALL
};
/**
* This sets different types of text coloring modes.
* When the coloring mode is not set to SOLID, the
* colors specified in setColorGradientCorners() determine
* the colors for the text.
* When the gradient mode is OVERALL, the coloring scheme
* attempts to approximate the effect as if the entire text box/region
* were a single polygon and you had applied colors to each of the four
* corners with GL_SMOOTH enabled. In this mode, OpenGL interpolates
* the colors across the polygon, and this is what OVERALL tries to
* emulate. This can be used to give nice embellishments on things
* like logos and names.
* PER_CHARACTER is similar to OVERALL except that it applies the
* color interpolation to the four corners of each character instead
* of across the overall text box.
* The default is SOLID (a.k.a. off).
*/
void setColorGradientMode(ColorGradientMode mode);
ColorGradientMode getColorGradientMode() const { return _colorGradientMode; }
/**
* Used only for gradient mode, let's you specify the colors of the 4 corners.
* If ColorGradients are off, these values are ignored (and the value from setColor()
* is the only one that is relevant.
*/
void setColorGradientCorners(const osg::Vec4& topLeft, const osg::Vec4& bottomLeft, const osg::Vec4& bottomRight, const osg::Vec4& topRight);
const osg::Vec4& getColorGradientTopLeft() const { return _colorGradientTopLeft; }
const osg::Vec4& getColorGradientBottomLeft() const { return _colorGradientBottomLeft; }
const osg::Vec4& getColorGradientBottomRight() const { return _colorGradientBottomRight; }
const osg::Vec4& getColorGradientTopRight() const { return _colorGradientTopRight; }
void setKerningType(KerningType kerningType) { _kerningType = kerningType; } void setKerningType(KerningType kerningType) { _kerningType = kerningType; }
KerningType getKerningType() const { return _kerningType; } KerningType getKerningType() const { return _kerningType; }
/** Get the number of wrapped lines - only valid after computeGlyphRepresentation() has been called, returns 0 otherwise */ /** Get the number of wrapped lines - only valid after computeGlyphRepresentation() has been called, returns 0 otherwise */
unsigned int getLineCount() const { return _lineCount; } unsigned int getLineCount() const { return _lineCount; }
/** Draw the text.*/ /** Draw the text.*/
virtual void drawImplementation(osg::State& state) const; virtual void drawImplementation(osg::State& state) const;
@ -254,6 +365,7 @@ public:
typedef std::vector<osg::Vec2> Coords2; typedef std::vector<osg::Vec2> Coords2;
typedef std::vector<osg::Vec3> Coords3; typedef std::vector<osg::Vec3> Coords3;
typedef std::vector<osg::Vec2> TexCoords; typedef std::vector<osg::Vec2> TexCoords;
typedef std::vector<osg::Vec4> ColorCoords;
Glyphs _glyphs; Glyphs _glyphs;
Coords2 _coords; Coords2 _coords;
@ -261,6 +373,9 @@ public:
TexCoords _texcoords; TexCoords _texcoords;
LineNumbers _lineNumbers; LineNumbers _lineNumbers;
osg::buffered_object<Coords3> _transformedBackdropCoords[8];
ColorCoords _colorCoords;
Glyphs getGlyphs() { return _glyphs; } Glyphs getGlyphs() { return _glyphs; }
const Glyphs getGlyphs() const { return _glyphs; } const Glyphs getGlyphs() const { return _glyphs; }
@ -272,11 +387,11 @@ public:
TexCoords& getTexCoords() { return _texcoords; } TexCoords& getTexCoords() { return _texcoords; }
const TexCoords& getTexCoords() const { return _texcoords; } const TexCoords& getTexCoords() const { return _texcoords; }
LineNumbers& getLineNumbers() { return _lineNumbers; } LineNumbers& getLineNumbers() { return _lineNumbers; }
const LineNumbers& getLineNumbers() const { return _lineNumbers; } const LineNumbers& getLineNumbers() const { return _lineNumbers; }
}; };
typedef std::map<osg::ref_ptr<osg::StateSet>,GlyphQuads> TextureGlyphQuadMap; typedef std::map<osg::ref_ptr<osg::StateSet>,GlyphQuads> TextureGlyphQuadMap;
/** Direct Access to GlyphQuads */ /** Direct Access to GlyphQuads */
@ -355,7 +470,27 @@ protected:
void computePositions(); void computePositions();
void computePositions(unsigned int contextID) const; void computePositions(unsigned int contextID) const;
void computeBackdropPositions(unsigned int contextID) const;
void computeColorGradients() const;
void computeColorGradientsOverall() const;
void computeColorGradientsPerCharacter() const;
BackdropType _backdropType;
float _backdropHorizontalOffset;
float _backdropVerticalOffset;
osg::Vec4 _backdropColor;
ColorGradientMode _colorGradientMode;
osg::Vec4 _colorGradientTopLeft;
osg::Vec4 _colorGradientBottomLeft;
osg::Vec4 _colorGradientBottomRight;
osg::Vec4 _colorGradientTopRight;
// Helper functions for color interpolation
float bilinearInterpolate(float x1, float x2, float y1, float y2, float x, float y, float q11, float q12, float q21, float q22) const;
void convertHsvToRgb( float hsv[], float rgb[] ) const;
void convertRgbToHsv( float rgb[], float hsv[] ) const;
}; };
} }

View File

@ -42,7 +42,16 @@ Text::Text():
_color(1.0f,1.0f,1.0f,1.0f), _color(1.0f,1.0f,1.0f,1.0f),
_drawMode(TEXT), _drawMode(TEXT),
_kerningType(KERNING_DEFAULT), _kerningType(KERNING_DEFAULT),
_lineCount(0) _lineCount(0),
_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)
{ {
setUseDisplayList(false); setUseDisplayList(false);
setSupportsDisplayList(false); setSupportsDisplayList(false);
@ -693,6 +702,7 @@ void Text::computeGlyphRepresentation()
setStateSet(const_cast<osg::StateSet*>((*_textureGlyphQuadMap.begin()).first.get())); setStateSet(const_cast<osg::StateSet*>((*_textureGlyphQuadMap.begin()).first.get()));
computePositions(); computePositions();
computeColorGradients();
} }
void Text::computePositions() void Text::computePositions()
@ -706,7 +716,6 @@ void Text::computePositions()
void Text::computePositions(unsigned int contextID) const void Text::computePositions(unsigned int contextID) const
{ {
switch(_alignment) switch(_alignment)
{ {
case LEFT_TOP: _offset.set(_textBB.xMin(),_textBB.yMax(),_textBB.zMin()); break; case LEFT_TOP: _offset.set(_textBB.xMin(),_textBB.yMax(),_textBB.zMin()); break;
@ -730,8 +739,6 @@ void Text::computePositions(unsigned int contextID) const
case RIGHT_BOTTOM_BASE_LINE: _offset.set(_textBB.xMax(),-_characterHeight*(_lineCount-1),0.0f); break; case RIGHT_BOTTOM_BASE_LINE: _offset.set(_textBB.xMax(),-_characterHeight*(_lineCount-1),0.0f); break;
} }
AutoTransformCache& atc = _autoTransformCache[contextID]; AutoTransformCache& atc = _autoTransformCache[contextID];
osg::Matrix& matrix = atc._matrix; osg::Matrix& matrix = atc._matrix;
@ -809,11 +816,9 @@ void Text::computePositions(unsigned int contextID) const
} }
if (_autoRotateToScreen) if (_autoRotateToScreen)
{ {
matrix.postMult(rotate_matrix); matrix.postMult(rotate_matrix);
} }
if (!_rotation.zeroRotation() ) if (!_rotation.zeroRotation() )
@ -834,8 +839,6 @@ void Text::computePositions(unsigned int contextID) const
matrix.makeTranslate(_position-_offset); matrix.makeTranslate(_position-_offset);
} }
// now apply matrix to the glyphs. // now apply matrix to the glyphs.
for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin(); for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end(); titr!=_textureGlyphQuadMap.end();
@ -857,12 +860,508 @@ void Text::computePositions(unsigned int contextID) const
} }
} }
computeBackdropPositions(contextID);
_normal = osg::Matrix::transform3x3(osg::Vec3(0.0f,0.0f,1.0f),matrix); _normal = osg::Matrix::transform3x3(osg::Vec3(0.0f,0.0f,1.0f),matrix);
_normal.normalize(); _normal.normalize();
const_cast<Text*>(this)->dirtyBound(); const_cast<Text*>(this)->dirtyBound();
} }
// Presumes the atc matrix is already up-to-date
void Text::computeBackdropPositions(unsigned int contextID) const
{
if(_backdropType == NONE)
{
return;
}
float width = 0.0f;
float height = 0.0f;
float running_width = 0.0f;
float running_height = 0.0f;
float avg_width = 0.0f;
float avg_height = 0.0f;
int counter = 0;
unsigned int i;
AutoTransformCache& atc = _autoTransformCache[contextID];
osg::Matrix& matrix = atc._matrix;
// 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.
for(TextureGlyphQuadMap::const_iterator const_titr=_textureGlyphQuadMap.begin();
const_titr!=_textureGlyphQuadMap.end();
++const_titr)
{
const GlyphQuads& glyphquad = const_titr->second;
const GlyphQuads::Coords2& coords2 = glyphquad._coords;
for(i = 0; i < coords2.size(); i+=4)
{
width = coords2[i+2].x() - coords2[i].x();
height = coords2[i].y() - coords2[i+1].y();
running_width += width;
running_height += height;
counter++;
}
}
avg_width = running_width/counter;
avg_height = running_height/counter;
// now apply matrix to the glyphs.
for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
GlyphQuads& glyphquad = titr->second;
GlyphQuads::Coords2& coords2 = 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::Coords3& transformedCoords = glyphquad._transformedBackdropCoords[backdrop_index][contextID];
unsigned int numCoords = coords2.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+coords2[i].x(),vertical_shift_direction * _backdropVerticalOffset * avg_height+coords2[i].y(),0.0f)*matrix;
}
}
}
// 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;
}
}
}
void Text::computeColorGradients() const
{
switch(_colorGradientMode)
{
case SOLID:
return;
break;
case PER_CHARACTER:
computeColorGradientsPerCharacter();
break;
case OVERALL:
computeColorGradientsOverall();
break;
default:
break;
}
}
void Text::computeColorGradientsOverall() const
{
float min_x = FLT_MAX;
float min_y = FLT_MAX;
float max_x = FLT_MIN;
float max_y = FLT_MIN;
float rgb_q11[3];
float hsv_q11[3];
float rgb_q12[3];
float hsv_q12[3];
float rgb_q21[3];
float hsv_q21[3];
float rgb_q22[3];
float hsv_q22[3];
float rgb[3];
float hsv[3];
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::Coords2& coords2 = glyphquad._coords;
for(i=0;i<coords2.size();++i)
{
// Min and Max are needed for color gradients
if(coords2[i].x() > max_x)
{
max_x = coords2[i].x();
}
if(coords2[i].x() < min_x)
{
min_x = coords2[i].x();
}
if(coords2[i].y() > max_y)
{
max_y = coords2[i].y();
}
if(coords2[i].y() < min_y)
{
min_y = coords2[i].y();
}
}
}
rgb_q11[0] = _colorGradientBottomLeft[0];
rgb_q11[1] = _colorGradientBottomLeft[1];
rgb_q11[2] = _colorGradientBottomLeft[2];
rgb_q12[0] = _colorGradientTopLeft[0];
rgb_q12[1] = _colorGradientTopLeft[1];
rgb_q12[2] = _colorGradientTopLeft[2];
rgb_q21[0] = _colorGradientBottomRight[0];
rgb_q21[1] = _colorGradientBottomRight[1];
rgb_q21[2] = _colorGradientBottomRight[2];
rgb_q22[0] = _colorGradientTopRight[0];
rgb_q22[1] = _colorGradientTopRight[1];
rgb_q22[2] = _colorGradientTopRight[2];
// for linear interpolation to look correct
// for colors and imitate what OpenGL does,
// we need to convert over to Hue-Saturation-Value
// and linear interpolate in that space.
// HSV will interpolate through the color spectrum.
// Now that I think about this, perhaps we could
// extend this to use function pointers or something
// so users may specify their own color interpolation
// scales such as Intensity, or Heated Metal, etc.
convertRgbToHsv(rgb_q11, hsv_q11);
convertRgbToHsv(rgb_q12, hsv_q12);
convertRgbToHsv(rgb_q21, hsv_q21);
convertRgbToHsv(rgb_q22, hsv_q22);
for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
GlyphQuads& glyphquad = titr->second;
GlyphQuads::Coords2& coords2 = glyphquad._coords;
GlyphQuads::ColorCoords& colorCoords = glyphquad._colorCoords;
unsigned int numCoords = coords2.size();
if (numCoords!=colorCoords.size())
{
colorCoords.resize(numCoords);
}
for(i=0;i<numCoords;++i)
{
float hue = bilinearInterpolate(
min_x,
max_x,
min_y,
max_y,
coords2[i].x(),
coords2[i].y(),
hsv_q11[0],
hsv_q12[0],
hsv_q21[0],
hsv_q22[0]
);
float saturation = bilinearInterpolate(
min_x,
max_x,
min_y,
max_y,
coords2[i].x(),
coords2[i].y(),
hsv_q11[1],
hsv_q12[1],
hsv_q21[1],
hsv_q22[1]
);
float value = bilinearInterpolate(
min_x,
max_x,
min_y,
max_y,
coords2[i].x(),
coords2[i].y(),
hsv_q11[2],
hsv_q12[2],
hsv_q21[2],
hsv_q22[2]
);
// Alpha does not convert to HSV
float alpha = bilinearInterpolate(
min_x,
max_x,
min_y,
max_y,
coords2[i].x(),
coords2[i].y(),
_colorGradientBottomLeft[3],
_colorGradientTopLeft[3],
_colorGradientBottomRight[3],
_colorGradientTopRight[3]
);
hsv[0] = hue;
hsv[1] = saturation;
hsv[2] = value;
// Convert back to RGB
convertHsvToRgb(hsv, rgb);
colorCoords[i] = osg::Vec4(rgb[0],rgb[1],rgb[2],alpha);
}
}
}
void Text::computeColorGradientsPerCharacter() const
{
for(TextureGlyphQuadMap::iterator titr=_textureGlyphQuadMap.begin();
titr!=_textureGlyphQuadMap.end();
++titr)
{
GlyphQuads& glyphquad = titr->second;
GlyphQuads::Coords2& coords2 = glyphquad._coords;
GlyphQuads::ColorCoords& colorCoords = glyphquad._colorCoords;
unsigned int numCoords = coords2.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::State& state) const void Text::drawImplementation(osg::State& state) const
{ {
unsigned int contextID = state.getContextID(); unsigned int contextID = state.getContextID();
@ -875,7 +1374,7 @@ void Text::drawImplementation(osg::State& state) const
const osg::Matrix& projection = state.getProjectionMatrix(); const osg::Matrix& projection = state.getProjectionMatrix();
osg::Vec3 newTransformedPosition = _position*modelview; osg::Vec3 newTransformedPosition = _position*modelview;
int width = atc._width; int width = atc._width;
int height = atc._height; int height = atc._height;
@ -932,14 +1431,19 @@ void Text::drawImplementation(osg::State& state) const
} }
glNormal3fv(_normal.ptr()); glNormal3fv(_normal.ptr());
glColor4fv(_color.ptr());
if (_drawMode & TEXT) if (_drawMode & TEXT)
{ {
state.disableAllVertexArrays(); state.disableAllVertexArrays();
for(TextureGlyphQuadMap::const_iterator titr=_textureGlyphQuadMap.begin(); if(_backdropType != NONE)
{
// 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!=_textureGlyphQuadMap.end();
++titr) ++titr)
{ {
@ -948,24 +1452,77 @@ void Text::drawImplementation(osg::State& state) const
const GlyphQuads& glyphquad = titr->second; const GlyphQuads& glyphquad = titr->second;
// For backdrop text
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, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front()));
state.disableColorPointer();
glColor4fv(_backdropColor.ptr());
for( ; backdrop_index < max_backdrop_index; backdrop_index++)
{
const GlyphQuads::Coords3& transformedBackdropCoords = glyphquad._transformedBackdropCoords[backdrop_index][contextID];
if (!transformedBackdropCoords.empty())
{
state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedBackdropCoords.front()));
glPolygonOffset(-1.0f, -1.0f * (backdrop_index+1) );
glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size());
}
}
} // end of backdrop text
const GlyphQuads::Coords3& transformedCoords = glyphquad._transformedCoords[contextID]; const GlyphQuads::Coords3& transformedCoords = glyphquad._transformedCoords[contextID];
if (!transformedCoords.empty()) if (!transformedCoords.empty())
{ {
state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedCoords.front())); state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedCoords.front()));
state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front()));
if(_colorGradientMode == SOLID)
{
state.disableColorPointer();
glColor4fv(_color.ptr());
}
else
{
state.setColorPointer( 4, GL_FLOAT, 0, &(glyphquad._colorCoords.front()));
}
if(_backdropType != NONE)
{
// Make sure that the main (foreground) text is on top
glPolygonOffset(-10, -10);
}
glDrawArrays(GL_QUADS,0,transformedCoords.size()); glDrawArrays(GL_QUADS,0,transformedCoords.size());
glPolygonOffset(0,0);
} }
} }
if(_backdropType != NONE)
{
glPopAttrib();
}
} }
if (_drawMode & BOUNDINGBOX) if (_drawMode & BOUNDINGBOX)
{ {
if (_textBB.valid()) if (_textBB.valid())
{ {
state.applyTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OFF); state.applyTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OFF);
const osg::Matrix& matrix = _autoTransformCache[contextID]._matrix; const osg::Matrix& matrix = _autoTransformCache[contextID]._matrix;
osg::Vec3 c00(osg::Vec3(_textBB.xMin(),_textBB.yMin(),_textBB.zMin())*matrix); osg::Vec3 c00(osg::Vec3(_textBB.xMin(),_textBB.yMin(),_textBB.zMin())*matrix);
@ -1007,9 +1564,6 @@ void Text::drawImplementation(osg::State& state) const
glEnd(); glEnd();
} }
// glPopMatrix();
} }
void Text::accept(osg::Drawable::ConstAttributeFunctor& af) const void Text::accept(osg::Drawable::ConstAttributeFunctor& af) const
@ -1047,3 +1601,251 @@ void Text::releaseGLObjects(osg::State* state) const
Drawable::releaseGLObjects(state); Drawable::releaseGLObjects(state);
if (_font.valid()) _font->releaseGLObjects(state); if (_font.valid()) _font->releaseGLObjects(state);
} }
void Text::setBackdropType(BackdropType type)
{
if (_backdropType==type) return;
_backdropType = type;
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;
computeGlyphRepresentation();
}
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))
);
}
/**
** routines to convert between RGB and HSV
**
** Reference: Foley, van Dam, Feiner, Hughes,
** "Computer Graphics Principles and Practices,"
** Additon-Wesley, 1990, pp592-593.
**/
/*
* FUNCTION
* HsvRgb( hsv, rgb )
*
* DESCRIPTION
* convert a hue-saturation-value into a red-green-blue value
*
* NOTE
* Array sizes are 3
* Values are between 0.0 and 1.0
*/
void Text::convertHsvToRgb( float hsv[], float rgb[] ) const
{
float h, s, v; /* hue, sat, value */
/* double delta; */ /* change in color value */
float r, g, b; /* red, green, blue */
float i, f, p, q, t; /* interim values */
/* guarantee valid input: */
h = hsv[0] / 60.f;
while( h >= 6.f ) h -= 6.f;
while( h < 0.f ) h += 6.f;
s = hsv[1];
if( s < 0.f )
s = 0.f;
if( s > 1.f )
s = 1.f;
v = hsv[2];
if( v < 0.f )
v = 0.f;
if( v > 1.f )
v = 1.f;
/* if sat==0, then is a gray: */
if( s == 0.0f )
{
rgb[0] = rgb[1] = rgb[2] = v;
return;
}
/* get an rgb from the hue itself: */
i = floor( h );
f = h - i;
p = v * ( 1.f - s );
q = v * ( 1.f - s*f );
t = v * ( 1.f - ( s * (1.f-f) ) );
switch( (int) i )
{
case 0:
r = v; g = t; b = p;
break;
case 1:
r = q; g = v; b = p;
break;
case 2:
r = p; g = v; b = t;
break;
case 3:
r = p; g = q; b = v;
break;
case 4:
r = t; g = p; b = v;
break;
case 5:
r = v; g = p; b = q;
break;
default:
/* never happens? */
r = 0; g = 0; b = 0;
break;
}
rgb[0] = r;
rgb[1] = g;
rgb[2] = b;
}
/*
* FUNCTION
* RgbHsv
*
* DESCRIPTION
* convert a red-green-blue value into hue-saturation-value
*
* NOTE
* Array sizes are 3
* Values are between 0.0 and 1.0
*/
void Text::convertRgbToHsv( float rgb[], float hsv[] ) const
{
float r, g, b; /* red, green, blue */
float min, max; /* min and max rgb values */
float fmin, fmax, diff; /* min, max, and range of rgb vals */
float hue, sat, value; /* h s v */
float cr, cg, cb; /* coefficients for computing hue */
/* determine min and max color primary values: */
r = rgb[0]; g = rgb[1]; b = rgb[2];
min = r; max = r;
if( g < min ) min = g;
if( g > max ) max = g;
if( b < min ) min = b;
if( b > max ) max = b;
fmin = min;
fmax = max;
diff = fmax - fmin;
/* get value and saturation: */
value = fmax;
if( max == 0.f )
sat = 0.0f;
else
sat = diff/fmax;
/* compute hue: */
if( sat == 0.0f )
hue = 0.0f;
else
{
float inv_diff = 1.0f / diff;
cr = ( fmax-r ) * inv_diff;
cg = ( fmax-g ) * inv_diff;
cb = ( fmax-b ) * inv_diff;
if( max == r )
hue = (g-b) * inv_diff;
else if( max == g )
hue = 2.f + (b-r) * inv_diff;
else if( max == b )
hue = 4.f + (r-g) * inv_diff;
else
hue = 0.0f;
}
hue *= 60.0f;
if( hue < 0.0f )
hue += 360.0f;
if( hue > 360.0f )
hue -= 360.0f;
/* store output values: */
hsv[0] = hue;
hsv[1] = sat;
hsv[2] = value;
}