/* -*-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 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) { 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 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;idirty(); } } } } // 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;isize();++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;isecond; 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;igetTexEnv()); #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() "<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."<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."<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."<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."<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 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); } }