/* -*-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 #include #include #include #include #include #include #include #include "DefaultFont.h" using namespace osg; using namespace osgText; //#define TREES_CODE_FOR_MAKING_SPACES_EDITABLE Text::Text(): _fontWidth(32), _fontHeight(32), _characterHeight(32), _characterAspectRatio(1.0f), _characterSizeMode(OBJECT_COORDS), _maximumWidth(0.0f), _maximumHeight(0.0f), _alignment(BASE_LINE), _autoRotateToScreen(false), _layout(LEFT_TO_RIGHT), _color(1.0f,1.0f,1.0f,1.0f), _drawMode(TEXT), _kerningType(KERNING_DEFAULT), _lineCount(0), _backdropType(NONE), _backdropImplementation(DEPTH_RANGE), _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) { setStateSet(DefaultFont::instance()->getStateSet()); setUseDisplayList(false); setSupportsDisplayList(false); } Text::Text(const Text& text,const osg::CopyOp& copyop): osg::Drawable(text,copyop), _font(text._font), _fontWidth(text._fontWidth), _fontHeight(text._fontHeight), _characterHeight(text._characterHeight), _characterAspectRatio(text._characterAspectRatio), _characterSizeMode(text._characterSizeMode), _maximumWidth(text._maximumWidth), _maximumHeight(text._maximumHeight), _text(text._text), _position(text._position), _alignment(text._alignment), _rotation(text._rotation), _autoRotateToScreen(text._autoRotateToScreen), _layout(text._layout), _color(text._color), _drawMode(text._drawMode), _kerningType(text._kerningType), _lineCount(text._lineCount), _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(Font* font) { if (_font==font) return; osg::StateSet* previousFontStateSet = _font.valid() ? _font->getStateSet() : DefaultFont::instance()->getStateSet(); osg::StateSet* newFontStateSet = font ? font->getStateSet() : DefaultFont::instance()->getStateSet(); if (getStateSet() == previousFontStateSet) { setStateSet( newFontStateSet ); } _font = font; computeGlyphRepresentation(); } void Text::setFont(const std::string& fontfile) { setFont(readFontFile(fontfile)); } void Text::setFontResolution(unsigned int width, unsigned int height) { _fontWidth = width; _fontHeight = height; computeGlyphRepresentation(); } void Text::setCharacterSize(float height,float aspectRatio) { _characterHeight = height; _characterAspectRatio = aspectRatio; computeGlyphRepresentation(); } void Text::setMaximumWidth(float maximumWidth) { _maximumWidth = maximumWidth; computeGlyphRepresentation(); } void Text::setMaximumHeight(float maximumHeight) { _maximumHeight = maximumHeight; computeGlyphRepresentation(); } void Text::setText(const String& text) { if (_text==text) return; _text = text; computeGlyphRepresentation(); } void Text::setText(const std::string& text) { setText(String(text)); } void Text::setText(const std::string& text,String::Encoding encoding) { setText(String(text,encoding)); } void Text::setText(const wchar_t* text) { setText(String(text)); } void Text::setPosition(const osg::Vec3& pos) { if (_position==pos) return; _position = pos; computePositions(); } void Text::setAlignment(AlignmentType alignment) { if (_alignment==alignment) return; _alignment = alignment; computePositions(); } void Text::setAxisAlignment(AxisAlignment axis) { switch(axis) { case XZ_PLANE: setRotation(osg::Quat(osg::inDegrees(90.0f),osg::Vec3(1.0f,0.0f,0.0f))); break; case REVERSED_XZ_PLANE: setRotation(osg::Quat(osg::inDegrees(180.0f),osg::Vec3(0.0f,1.0f,0.0f))* osg::Quat(osg::inDegrees(90.0f),osg::Vec3(1.0f,0.0f,0.0f))); break; case YZ_PLANE: setRotation(osg::Quat(osg::inDegrees(90.0f),osg::Vec3(1.0f,0.0f,0.0f))* osg::Quat(osg::inDegrees(90.0f),osg::Vec3(0.0f,0.0f,1.0f))); break; case REVERSED_YZ_PLANE: setRotation(osg::Quat(osg::inDegrees(180.0f),osg::Vec3(0.0f,1.0f,0.0f))* osg::Quat(osg::inDegrees(90.0f),osg::Vec3(1.0f,0.0f,0.0f))* osg::Quat(osg::inDegrees(90.0f),osg::Vec3(0.0f,0.0f,1.0f))); break; case XY_PLANE: setRotation(osg::Quat()); // nop - already on XY plane. break; case REVERSED_XY_PLANE: setRotation(osg::Quat(osg::inDegrees(180.0f),osg::Vec3(0.0f,1.0f,0.0f))); break; case SCREEN: setAutoRotateToScreen(true); break; } } void Text::setRotation(const osg::Quat& quat) { _rotation = quat; computePositions(); } void Text::setAutoRotateToScreen(bool autoRotateToScreen) { if (_autoRotateToScreen==autoRotateToScreen) return; _autoRotateToScreen = autoRotateToScreen; } void Text::setLayout(Layout layout) { if (_layout==layout) return; _layout = layout; computeGlyphRepresentation(); } void Text::setColor(const osg::Vec4& color) { _color = color; } void Text::setDrawMode(unsigned int mode) { if (_drawMode==mode) return; _drawMode=mode; } osg::BoundingBox Text::computeBound() const { osg::BoundingBox bbox; if (_textBB.valid()) { for(unsigned int i=0;i<_autoTransformCache.size();++i) { if (_autoTransformCache[i]._traversalNumber<0 && (_characterSizeMode!=OBJECT_COORDS || _autoRotateToScreen)) { // _autoTransformCache is not valid so don't take it into accoumt when compute bounding volume. } else { osg::Matrix& matrix = _autoTransformCache[i]._matrix; bbox.expandBy(osg::Vec3(_textBB.xMin(),_textBB.yMin(),_textBB.zMin())*matrix); bbox.expandBy(osg::Vec3(_textBB.xMax(),_textBB.yMin(),_textBB.zMin())*matrix); bbox.expandBy(osg::Vec3(_textBB.xMax(),_textBB.yMax(),_textBB.zMin())*matrix); bbox.expandBy(osg::Vec3(_textBB.xMin(),_textBB.yMax(),_textBB.zMin())*matrix); } } } return bbox; } Font* Text::getActiveFont() { return _font.valid() ? _font.get() : DefaultFont::instance(); } const Font* Text::getActiveFont() const { return _font.valid() ? _font.get() : DefaultFont::instance(); } String::iterator Text::computeLastCharacterOnLine(osg::Vec2& cursor, String::iterator first,String::iterator last) { Font* activefont = getActiveFont(); if (!activefont) return last; float hr = _characterHeight/(float)activefont->getFontHeight(); float wr = hr/_characterAspectRatio; bool kerning = true; unsigned int previous_charcode = 0; String::iterator lastChar = first; std::set deliminatorSet; deliminatorSet.insert(' '); deliminatorSet.insert('\n'); deliminatorSet.insert(':'); deliminatorSet.insert('/'); deliminatorSet.insert(','); deliminatorSet.insert(';'); deliminatorSet.insert(':'); deliminatorSet.insert('.'); for(bool outOfSpace=false;lastChar!=last;++lastChar) { unsigned int charcode = *lastChar; if (charcode=='\n') { return lastChar; } Font::Glyph* glyph = activefont->getGlyph(charcode); if (glyph) { float width = (float)(glyph->s()-2*activefont->getGlyphImageMargin()) * wr; //float height = (float)(glyph->t()-2*activefont->getGlyphImageMargin()) * hr; #ifdef TREES_CODE_FOR_MAKING_SPACES_EDITABLE if (width == 0.0f) width = glyph->getHorizontalAdvance() * wr; //if (height == 0.0f) height = glyph->getVerticalAdvance() * hr; #endif 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(previous_charcode,charcode,_kerningType)); cursor.x() += delta.x() * wr; cursor.y() += delta.y() * hr; break; } case RIGHT_TO_LEFT: { osg::Vec2 delta(activefont->getKerning(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; break; } case RIGHT_TO_LEFT: { if (_maximumWidth>0.0f && cursor.x()<-_maximumWidth) 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) { if (deliminatorSet.count(*lastChar)==0) { String::iterator lastValidChar = lastChar; while (lastValidChar!=first && deliminatorSet.count(*lastValidChar)==0) { --lastValidChar; //Substract off glyphs from the cursor position (to correctly center text) Font::Glyph* glyph = activefont->getGlyph(*lastValidChar); 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. } } } if (first!=lastValidChar) { ++lastValidChar; lastChar = lastValidChar; } } } 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; } // 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; unsigned int lineNumber = 0; activefont->setFontResolution(_fontWidth,_fontHeight); float hr = _characterHeight/(float)activefont->getFontHeight(); float wr = hr/_characterAspectRatio; 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 aligment - Question is this neccesary? // ... 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; Font::Glyph* glyph = activefont->getGlyph(charcode); if (glyph) { float width = (float)(glyph->s()-2*activefont->getGlyphImageMargin()) * wr; float height = (float)(glyph->t()-2*activefont->getGlyphImageMargin()) * hr; #ifdef TREES_CODE_FOR_MAKING_SPACES_EDITABLE if (width == 0.0f) width = glyph->getHorizontalAdvance() * wr; if (height == 0.0f) height = glyph->getVerticalAdvance() * hr; #endif 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(previous_charcode,charcode,_kerningType)); cursor.x() += delta.x() * wr; cursor.y() += delta.y() * hr; break; } case RIGHT_TO_LEFT: { osg::Vec2 delta(activefont->getKerning(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; GlyphQuads& glyphquad = _textureGlyphQuadMap[glyph->getTexture()]; glyphquad._glyphs.push_back(glyph); glyphquad._lineNumbers.push_back(lineNumber); // set up the coords of the quad glyphquad._coords.push_back(local+osg::Vec2(0.0f,height)); glyphquad._coords.push_back(local+osg::Vec2(0.0f,0.0f)); glyphquad._coords.push_back(local+osg::Vec2(width,0.0f)); glyphquad._coords.push_back(local+osg::Vec2(width,height)); // set up the tex coords of the quad const osg::Vec2& mintc = glyph->getMinTexCoord(); const osg::Vec2& maxtc = glyph->getMaxTexCoord(); glyphquad._texcoords.push_back(osg::Vec2(mintc.x(),maxtc.y())); glyphquad._texcoords.push_back(osg::Vec2(mintc.x(),mintc.y())); glyphquad._texcoords.push_back(osg::Vec2(maxtc.x(),mintc.y())); glyphquad._texcoords.push_back(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(local.x(),local.y(),0.0f)); //lower left corner _textBB.expandBy(osg::Vec3(cursor.x(),local.y()+height,0.0f)); //upper right corner break; case VERTICAL: cursor.y() -= glyph->getVerticalAdvance() *hr; _textBB.expandBy(osg::Vec3(local.x(),local.y()+height,0.0f)); //upper left corner _textBB.expandBy(osg::Vec3(local.x()+width,cursor.y(),0.0f)); //lower right corner break; case RIGHT_TO_LEFT: _textBB.expandBy(osg::Vec3(local.x()+width,local.y(),0.0f)); //lower right corner _textBB.expandBy(osg::Vec3(cursor.x(),local.y()+height,0.0f)); //upper left corner break; } previous_charcode = charcode; } } } else { ++itr; } if (itr!=_text.end()) { // skip over return. if (*itr=='\n') ++itr; } // move to new line. switch(_layout) { case LEFT_TO_RIGHT: { startOfLine_coords.y() -= _characterHeight; cursor = startOfLine_coords; previous_charcode = 0; _lineCount++; break; } case RIGHT_TO_LEFT: { startOfLine_coords.y() -= _characterHeight; cursor = startOfLine_coords; previous_charcode = 0; _lineCount++; break; } case VERTICAL: { startOfLine_coords.x() += _characterHeight/_characterAspectRatio; cursor = startOfLine_coords; previous_charcode = 0; // because _lineCount is the max vertical no. of characters.... _lineCount = (_lineCount >linelength)?_lineCount:linelength; } break; } ++lineNumber; } computePositions(); computeBackdropBoundingBox(); computeColorGradients(); } // 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::computeAverageGlypthWidthAndHeight(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::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++; } } if(0 == counter) { is_valid_size = false; } else { avg_width = running_width/counter; avg_height = running_height/counter; } return is_valid_size; } void Text::computePositions() { unsigned int size = osg::maximum(osg::DisplaySettings::instance()->getMaxNumberOfGraphicsContexts(),_autoTransformCache.size()); // FIXME: OPTIMIZE: This would be one of the ideal locations to // call computeAverageGlypthWidthAndHeight(). It is out of the contextID loop // so the value would be computed fewer times. But the code will need changes // to get the value down to the locations it is needed. (Either pass through parameters // or member variables, but we would need a system to know if the values are stale.) for(unsigned int i=0;i_fontHeight) { float scale_font = _fontHeight/pixelSizeVert; matrix.postMult(osg::Matrix::scale(scale_font, scale_font,1.0f)); } } if (_autoRotateToScreen) { matrix.postMult(rotate_matrix); } matrix.postMult(osg::Matrix::translate(_position)); } else if (!_rotation.zeroRotation()) { matrix.makeTranslate(-_offset); matrix.postMult(osg::Matrix::rotate(_rotation)); matrix.postMult(osg::Matrix::translate(_position)); } else { matrix.makeTranslate(_position-_offset); } // 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; GlyphQuads::Coords3& transformedCoords = glyphquad._transformedCoords[contextID]; unsigned int numCoords = coords2.size(); if (numCoords!=transformedCoords.size()) { transformedCoords.resize(numCoords); } for(unsigned int i=0;i(this)->dirtyBound(); } // Presumes the atc matrix is already up-to-date void Text::computeBackdropPositions(unsigned int contextID) const { if(_backdropType == NONE) { return; } float avg_width = 0.0f; float avg_height = 0.0f; unsigned int i; bool is_valid_size; AutoTransformCache& atc = _autoTransformCache[contextID]; osg::Matrix& matrix = atc._matrix; // 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 = computeAverageGlypthWidthAndHeight(avg_width, avg_height); // 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;isecond; const GlyphQuads::Coords2& coords2 = glyphquad._coords; for(i=0;i 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;isecond; 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;igetTexEnv()); if (_characterSizeMode!=OBJECT_COORDS || _autoRotateToScreen) { int frameNumber = state.getFrameStamp()?state.getFrameStamp()->getFrameNumber():0; AutoTransformCache& atc = _autoTransformCache[contextID]; const osg::Matrix& modelview = state.getModelViewMatrix(); const osg::Matrix& projection = state.getProjectionMatrix(); osg::Vec3 newTransformedPosition = _position*modelview; int width = atc._width; int height = atc._height; const osg::Viewport* viewport = state.getCurrentViewport(); if (viewport) { width = viewport->width(); height = viewport->height(); } bool doUpdate = atc._traversalNumber==-1; if (atc._traversalNumber>=0) { if (atc._modelview!=modelview) { doUpdate = true; } else if (width!=atc._width || height!=atc._height) { doUpdate = true; } else if (atc._projection!=projection) { doUpdate = true; } } atc._traversalNumber = frameNumber; atc._width = width; atc._height = height; if (doUpdate) { atc._transformedPosition = newTransformedPosition; atc._projection = projection; atc._modelview = modelview; computePositions(contextID); } } // Ensure that the glyph coordinates have been transformed for // this context id. if ( !_textureGlyphQuadMap.empty() ) { const GlyphQuads& glyphquad = (_textureGlyphQuadMap.begin())->second; if ( glyphquad._transformedCoords[contextID].empty() ) { computePositions(contextID); } } glNormal3fv(_normal.ptr()); 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) { switch(_backdropImplementation) { case POLYGON_OFFSET: renderWithPolygonOffset(state); break; case NO_DEPTH_BUFFER: renderWithNoDepthBuffer(state); break; case DEPTH_RANGE: renderWithDepthRange(state); break; case STENCIL_BUFFER: renderWithStencilBuffer(state); break; default: renderWithPolygonOffset(state); } } else { renderOnlyForegroundText(state); } } if (_drawMode & BOUNDINGBOX) { if (_textBB.valid()) { state.applyTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OFF); const osg::Matrix& matrix = _autoTransformCache[contextID]._matrix; osg::Vec3 c00(osg::Vec3(_textBB.xMin(),_textBB.yMin(),_textBB.zMin())*matrix); osg::Vec3 c10(osg::Vec3(_textBB.xMax(),_textBB.yMin(),_textBB.zMin())*matrix); osg::Vec3 c11(osg::Vec3(_textBB.xMax(),_textBB.yMax(),_textBB.zMin())*matrix); osg::Vec3 c01(osg::Vec3(_textBB.xMin(),_textBB.yMax(),_textBB.zMin())*matrix); glColor4f(1.0f,1.0f,0.0f,1.0f); glBegin(GL_LINE_LOOP); glVertex3fv(c00.ptr()); glVertex3fv(c10.ptr()); glVertex3fv(c11.ptr()); glVertex3fv(c01.ptr()); glEnd(); } } if (_drawMode & ALIGNMENT) { glColor4f(1.0f,0.0f,1.0f,1.0f); float cursorsize = _characterHeight*0.5f; const osg::Matrix& matrix = _autoTransformCache[contextID]._matrix; osg::Vec3 hl(osg::Vec3(_offset.x()-cursorsize,_offset.y(),_offset.z())*matrix); osg::Vec3 hr(osg::Vec3(_offset.x()+cursorsize,_offset.y(),_offset.z())*matrix); osg::Vec3 vt(osg::Vec3(_offset.x(),_offset.y()-cursorsize,_offset.z())*matrix); osg::Vec3 vb(osg::Vec3(_offset.x(),_offset.y()+cursorsize,_offset.z())*matrix); state.applyTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OFF); glBegin(GL_LINES); glVertex3fv(hl.ptr()); glVertex3fv(hr.ptr()); glVertex3fv(vt.ptr()); glVertex3fv(vb.ptr()); glEnd(); } } void Text::accept(osg::Drawable::ConstAttributeFunctor& af) const { for(TextureGlyphQuadMap::const_iterator titr=_textureGlyphQuadMap.begin(); titr!=_textureGlyphQuadMap.end(); ++titr) { const GlyphQuads& glyphquad = titr->second; af.apply(osg::Drawable::VERTICES,glyphquad._transformedCoords[0].size(),&(glyphquad._transformedCoords[0].front())); af.apply(osg::Drawable::TEXTURE_COORDS_0,glyphquad._texcoords.size(),&(glyphquad._texcoords.front())); } } void Text::accept(osg::PrimitiveFunctor& pf) const { for(TextureGlyphQuadMap::const_iterator titr=_textureGlyphQuadMap.begin(); titr!=_textureGlyphQuadMap.end(); ++titr) { const GlyphQuads& glyphquad = titr->second; pf.setVertexArray(glyphquad._transformedCoords[0].size(),&(glyphquad._transformedCoords[0].front())); pf.drawArrays(GL_QUADS,0,glyphquad._transformedCoords[0].size()); } } /** If State is non-zero, this function releases OpenGL objects for * the specified graphics context. Otherwise, releases OpenGL objexts * for all graphics contexts. */ void Text::releaseGLObjects(osg::State* state) const { Drawable::releaseGLObjects(state); if (_font.valid()) _font->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; 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; } void Text::drawForegroundText(osg::State& state, const GlyphQuads& glyphquad) const { unsigned int contextID = state.getContextID(); const GlyphQuads::Coords3& transformedCoords = glyphquad._transformedCoords[contextID]; if (!transformedCoords.empty()) { state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedCoords.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())); } glDrawArrays(GL_QUADS,0,transformedCoords.size()); } } void Text::renderOnlyForegroundText(osg::State& state) 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); } } void Text::renderWithPolygonOffset(osg::State& state) const { unsigned int contextID = state.getContextID(); 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, 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(0.1f * osg::PolygonOffset::getFactorMultiplier(), 2.0f * osg::PolygonOffset::getUnitsMultiplier() * (max_backdrop_index-backdrop_index) ); glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); } } // Reset the polygon offset so the foreground text is on top glPolygonOffset(0.0f,0.0f); drawForegroundText(state, glyphquad); } glPopAttrib(); } void Text::renderWithNoDepthBuffer(osg::State& state) const { unsigned int contextID = state.getContextID(); 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, 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())); glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); } } drawForegroundText(state, glyphquad); } glPopAttrib(); } // This idea comes from Paul Martz's OpenGL FAQ: 13.050 void Text::renderWithDepthRange(osg::State& state) const { unsigned int contextID = state.getContextID(); // 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, 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())); double offset = double(max_backdrop_index-backdrop_index)*0.003; glDepthRange( offset, 1.0+offset); glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); } } glDepthRange(0.0, 1.0); drawForegroundText(state, glyphquad); } glPopAttrib(); } void Text::renderWithStencilBuffer(osg::State& state) const { /* 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. */ unsigned int contextID = state.getContextID(); TextureGlyphQuadMap::iterator titr; // Moved up here for VC6 glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_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, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); state.disableColorPointer(); 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())); glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); } } // Draw the foreground text const GlyphQuads::Coords3& transformedCoords = glyphquad._transformedCoords[contextID]; if (!transformedCoords.empty()) { state.setVertexPointer( 3, GL_FLOAT, 0, &(transformedCoords.front())); state.setTexCoordPointer( 0, 2, GL_FLOAT, 0, &(glyphquad._texcoords.front())); glDrawArrays(GL_QUADS,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, 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())); glDrawArrays(GL_QUADS,0,transformedBackdropCoords.size()); } } drawForegroundText(state, glyphquad); } glPopAttrib(); }