Hi Robert, There has always been an issue with placing AutoTransforms and auto scaled Text under an LOD b/c the bounding sphere of the node isn't initialized until the first time the AutoTransform/Text is traversed and the LOD doesn't allow the traversal b/c the Node doesn't have a valid bounding sphere, so you end up with a chicken and egg type of problem. In the past I was able to get around this issue by clever usage of setInitialBound but I'm in a situation now where I'm not going to be able to know exactly what the initial bound should be set to.
I played around with AutoTransform and TextBase and found that simply removing the custom computeBound in AutoTransform and just using the default Transform implementation works great great since the bound is dirtied when changes are made / nodes added etc to the AutoTransform. For TextBase I enabled the code block that was #ifdefed out in TextBase.cpp that tries to compute an approximate bound if the node hasn't been traversed and it works well. There is a comment in there about it not working well with small feature culling but I haven't seen any issues since I made the change. Thanks! Jason
/* -*-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 <osg/AutoTransform>
#include <osg/CullStack>
#include <osg/Notify>
#include <osg/io_utils>
using namespace osg;
AutoTransform::AutoTransform():
_autoUpdateEyeMovementTolerance(0.0),
_autoRotateMode(NO_ROTATION),
_autoScaleToScreen(false),
_scale(1.0,1.0,1.0),
_firstTimeToInitEyePoint(true),
_minimumScale(0.0),
_maximumScale(DBL_MAX),
_autoScaleTransitionWidthRatio(0.25),
_matrixDirty(true),
_axis(0.0f,0.0f,1.0f),
_normal(0.0f,-1.0f,0.0f),
_cachedMode(NO_ROTATION),
_side(1.0f,0.0,0.0f)
{
// setNumChildrenRequiringUpdateTraversal(1);
}
AutoTransform::AutoTransform(const AutoTransform& pat,const CopyOp& copyop):
Transform(pat,copyop),
_position(pat._position),
_pivotPoint(pat._pivotPoint),
_autoUpdateEyeMovementTolerance(pat._autoUpdateEyeMovementTolerance),
_autoRotateMode(pat._autoRotateMode),
_autoScaleToScreen(pat._autoScaleToScreen),
_rotation(pat._rotation),
_scale(pat._scale),
_firstTimeToInitEyePoint(true),
_minimumScale(pat._minimumScale),
_maximumScale(pat._maximumScale),
_autoScaleTransitionWidthRatio(pat._autoScaleTransitionWidthRatio),
_matrixDirty(true),
_axis(pat._axis),
_normal(pat._normal),
_cachedMode(pat._cachedMode),
_side(pat._side)
{
// setNumChildrenRequiringUpdateTraversal(getNumChildrenRequiringUpdateTraversal()+1);
}
void AutoTransform::setAutoRotateMode(AutoRotateMode mode)
{
_autoRotateMode = mode;
_firstTimeToInitEyePoint = true;
_cachedMode = CACHE_DIRTY;
updateCache();
}
void AutoTransform::setAxis(const Vec3& axis)
{
_axis = axis;
_axis.normalize();
updateCache();
}
void AutoTransform::setNormal(const Vec3& normal)
{
_normal = normal;
_normal.normalize();
updateCache();
}
void AutoTransform::updateCache()
{
if (_autoRotateMode==ROTATE_TO_AXIS)
{
if (_axis==Vec3(1.0f,0.0,0.0f) && _normal==Vec3(0.0f,-1.0,0.0f)) _cachedMode = AXIAL_ROT_X_AXIS;
else if (_axis==Vec3(0.0f,1.0,0.0f) && _normal==Vec3(1.0f, 0.0,0.0f)) _cachedMode = AXIAL_ROT_Y_AXIS;
else if (_axis==Vec3(0.0f,0.0,1.0f) && _normal==Vec3(0.0f,-1.0,0.0f)) _cachedMode = AXIAL_ROT_Z_AXIS;
else _cachedMode = ROTATE_TO_AXIS;
}
else _cachedMode = _autoRotateMode;
_side = _axis^_normal;
_side.normalize();
}
void AutoTransform::setScale(const Vec3d& scale)
{
_scale = scale;
if (_scale.x()<_minimumScale) _scale.x() = _minimumScale;
if (_scale.y()<_minimumScale) _scale.y() = _minimumScale;
if (_scale.z()<_minimumScale) _scale.z() = _minimumScale;
if (_scale.x()>_maximumScale) _scale.x() = _maximumScale;
if (_scale.y()>_maximumScale) _scale.y() = _maximumScale;
if (_scale.z()>_maximumScale) _scale.z() = _maximumScale;
_matrixDirty=true;
dirtyBound();
}
bool AutoTransform::computeLocalToWorldMatrix(Matrix& matrix,NodeVisitor*) const
{
if (_matrixDirty) computeMatrix();
if (_referenceFrame==RELATIVE_RF)
{
matrix.preMult(_cachedMatrix);
}
else // absolute
{
matrix = _cachedMatrix;
}
return true;
}
bool AutoTransform::computeWorldToLocalMatrix(Matrix& matrix,NodeVisitor*) const
{
if (_scale.x() == 0.0 || _scale.y() == 0.0 || _scale.z() == 0.0)
return false;
if (_referenceFrame==RELATIVE_RF)
{
matrix.postMultTranslate(-_position);
matrix.postMultRotate(_rotation.inverse());
matrix.postMultScale(Vec3d(1.0/_scale.x(), 1.0/_scale.y(), 1.0/_scale.z()));
matrix.postMultTranslate(_pivotPoint);
}
else // absolute
{
matrix.makeRotate(_rotation.inverse());
matrix.preMultTranslate(-_position);
matrix.postMultScale(Vec3d(1.0/_scale.x(), 1.0/_scale.y(), 1.0/_scale.z()));
matrix.postMultTranslate(_pivotPoint);
}
return true;
}
void AutoTransform::computeMatrix() const
{
if (!_matrixDirty) return;
_cachedMatrix.makeRotate(_rotation);
_cachedMatrix.postMultTranslate(_position);
_cachedMatrix.preMultScale(_scale);
_cachedMatrix.preMultTranslate(-_pivotPoint);
_matrixDirty = false;
}
void AutoTransform::accept(NodeVisitor& nv)
{
if (nv.validNodeMask(*this))
{
// if app traversal update the frame count.
if (nv.getVisitorType()==NodeVisitor::UPDATE_VISITOR)
{
}
else
if (nv.getVisitorType()==NodeVisitor::CULL_VISITOR)
{
CullStack* cs = dynamic_cast<CullStack*>(&nv);
if (cs)
{
Viewport::value_type width = _previousWidth;
Viewport::value_type height = _previousHeight;
osg::Viewport* viewport = cs->getViewport();
if (viewport)
{
width = viewport->width();
height = viewport->height();
}
osg::Vec3d eyePoint = cs->getEyeLocal();
osg::Vec3d localUp = cs->getUpLocal();
osg::Vec3d position = getPosition();
const osg::Matrix& projection = *(cs->getProjectionMatrix());
bool doUpdate = _firstTimeToInitEyePoint;
if (!_firstTimeToInitEyePoint)
{
osg::Vec3d dv = _previousEyePoint-eyePoint;
if (dv.length2()>getAutoUpdateEyeMovementTolerance()*(eyePoint-getPosition()).length2())
{
doUpdate = true;
}
osg::Vec3d dupv = _previousLocalUp-localUp;
// rotating the camera only affects ROTATE_TO_*
if (_autoRotateMode &&
dupv.length2()>getAutoUpdateEyeMovementTolerance())
{
doUpdate = true;
}
else if (width!=_previousWidth || height!=_previousHeight)
{
doUpdate = true;
}
else if (projection != _previousProjection)
{
doUpdate = true;
}
else if (position != _previousPosition)
{
doUpdate = true;
}
}
_firstTimeToInitEyePoint = false;
if (doUpdate)
{
if (getAutoScaleToScreen())
{
double size = 1.0/cs->pixelSize(getPosition(),0.48f);
if (_autoScaleTransitionWidthRatio>0.0)
{
if (_minimumScale>0.0)
{
double j = _minimumScale;
double i = (_maximumScale<DBL_MAX) ?
_minimumScale+(_maximumScale-_minimumScale)*_autoScaleTransitionWidthRatio :
_minimumScale*(1.0+_autoScaleTransitionWidthRatio);
double c = 1.0/(4.0*(i-j));
double b = 1.0 - 2.0*c*i;
double a = j + b*b / (4.0*c);
double k = -b / (2.0*c);
if (size<k) size = _minimumScale;
else if (size<i) size = a + b*size + c*(size*size);
}
if (_maximumScale<DBL_MAX)
{
double n = _maximumScale;
double m = (_minimumScale>0.0) ?
_maximumScale+(_minimumScale-_maximumScale)*_autoScaleTransitionWidthRatio :
_maximumScale*(1.0-_autoScaleTransitionWidthRatio);
double c = 1.0 / (4.0*(m-n));
double b = 1.0 - 2.0*c*m;
double a = n + b*b/(4.0*c);
double p = -b / (2.0*c);
if (size>p) size = _maximumScale;
else if (size>m) size = a + b*size + c*(size*size);
}
}
setScale(size);
}
if (_autoRotateMode==ROTATE_TO_SCREEN)
{
osg::Vec3d translation;
osg::Quat rotation;
osg::Vec3d scale;
osg::Quat so;
cs->getModelViewMatrix()->decompose( translation, rotation, scale, so );
setRotation(rotation.inverse());
}
else if (_autoRotateMode==ROTATE_TO_CAMERA)
{
osg::Vec3d PosToEye = _position - eyePoint;
osg::Matrix lookto = osg::Matrix::lookAt(
osg::Vec3d(0,0,0), PosToEye, localUp);
Quat q;
q.set(osg::Matrix::inverse(lookto));
setRotation(q);
}
else if (_autoRotateMode==ROTATE_TO_AXIS)
{
Matrix matrix;
Vec3 ev(eyePoint - _position);
switch(_cachedMode)
{
case(AXIAL_ROT_Z_AXIS):
{
ev.z() = 0.0f;
float ev_length = ev.length();
if (ev_length>0.0f)
{
//float rotation_zrotation_z = atan2f(ev.x(),ev.y());
//mat.makeRotate(inRadians(rotation_z),0.0f,0.0f,1.0f);
float inv = 1.0f/ev_length;
float s = ev.x()*inv;
float c = -ev.y()*inv;
matrix(0,0) = c;
matrix(1,0) = -s;
matrix(0,1) = s;
matrix(1,1) = c;
}
break;
}
case(AXIAL_ROT_Y_AXIS):
{
ev.y() = 0.0f;
float ev_length = ev.length();
if (ev_length>0.0f)
{
//float rotation_zrotation_z = atan2f(ev.x(),ev.y());
//mat.makeRotate(inRadians(rotation_z),0.0f,0.0f,1.0f);
float inv = 1.0f/ev_length;
float s = -ev.z()*inv;
float c = ev.x()*inv;
matrix(0,0) = c;
matrix(2,0) = s;
matrix(0,2) = -s;
matrix(2,2) = c;
}
break;
}
case(AXIAL_ROT_X_AXIS):
{
ev.x() = 0.0f;
float ev_length = ev.length();
if (ev_length>0.0f)
{
//float rotation_zrotation_z = atan2f(ev.x(),ev.y());
//mat.makeRotate(inRadians(rotation_z),0.0f,0.0f,1.0f);
float inv = 1.0f/ev_length;
float s = -ev.z()*inv;
float c = -ev.y()*inv;
matrix(1,1) = c;
matrix(2,1) = -s;
matrix(1,2) = s;
matrix(2,2) = c;
}
break;
}
case(ROTATE_TO_AXIS): // need to implement
{
float ev_side = ev*_side;
float ev_normal = ev*_normal;
float rotation = atan2f(ev_side,ev_normal);
matrix.makeRotate(rotation,_axis);
break;
}
}
Quat q;
q.set(matrix);
setRotation(q);
}
_previousEyePoint = eyePoint;
_previousLocalUp = localUp;
_previousWidth = width;
_previousHeight = height;
_previousProjection = projection;
_previousPosition = position;
_matrixDirty = true;
}
}
}
// now do the proper accept
Transform::accept(nv);
}
}/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#include <osgText/TextBase>
#include <osgText/Font>
#include <osg/Math>
#include <osg/GL>
#include <osg/Notify>
#include <osg/PolygonOffset>
#include <osg/TexEnv>
#include <osgUtil/CullVisitor>
#include <osgDB/ReadFile>
using namespace osg;
using namespace osgText;
//#define TREES_CODE_FOR_MAKING_SPACES_EDITABLE
TextBase::TextBase():
_color(1.0f,1.0f,1.0f,1.0f),
_fontSize(32,32),
_characterHeight(32),
_characterSizeMode(OBJECT_COORDS),
_maximumWidth(0.0f),
_maximumHeight(0.0f),
_lineSpacing(0.0f),
_alignment(BASE_LINE),
_axisAlignment(XY_PLANE),
_autoRotateToScreen(false),
_layout(LEFT_TO_RIGHT),
_drawMode(TEXT),
_textBBMargin(0.0f),
_textBBColor(0.0, 0.0, 0.0, 0.5),
_kerningType(KERNING_DEFAULT),
_lineCount(0)
{
setStateSet(Font::getDefaultFont()->getStateSet());
setUseDisplayList(false);
setSupportsDisplayList(false);
}
TextBase::TextBase(const TextBase& textBase,const osg::CopyOp& copyop):
osg::Drawable(textBase,copyop),
_color(textBase._color),
_font(textBase._font),
_style(textBase._style),
_fontSize(textBase._fontSize),
_characterHeight(textBase._characterHeight),
_characterSizeMode(textBase._characterSizeMode),
_maximumWidth(textBase._maximumWidth),
_maximumHeight(textBase._maximumHeight),
_lineSpacing(textBase._lineSpacing),
_text(textBase._text),
_position(textBase._position),
_alignment(textBase._alignment),
_axisAlignment(textBase._axisAlignment),
_rotation(textBase._rotation),
_autoRotateToScreen(textBase._autoRotateToScreen),
_layout(textBase._layout),
_drawMode(textBase._drawMode),
_textBBMargin(textBase._textBBMargin),
_textBBColor(textBase._textBBColor),
_kerningType(textBase._kerningType),
_lineCount(textBase._lineCount)
{
}
TextBase::~TextBase()
{
}
void TextBase::setColor(const osg::Vec4& color)
{
_color = color;
}
void TextBase::setFont(osg::ref_ptr<Font> font)
{
if (_font==font) return;
osg::StateSet* previousFontStateSet = _font.valid() ? _font->getStateSet() : Font::getDefaultFont()->getStateSet();
osg::StateSet* newFontStateSet = font.valid() ? font->getStateSet() : Font::getDefaultFont()->getStateSet();
if (getStateSet() == previousFontStateSet)
{
setStateSet( newFontStateSet );
}
_font = font;
computeGlyphRepresentation();
}
void TextBase::setFont(const std::string& fontfile)
{
setFont(readRefFontFile(fontfile));
}
void TextBase::setFontResolution(unsigned int width, unsigned int height)
{
_fontSize = FontResolution(width,height);
computeGlyphRepresentation();
}
void TextBase::setCharacterSize(float height)
{
_characterHeight = height;
computeGlyphRepresentation();
}
void TextBase::setCharacterSize(float height, float aspectRatio)
{
if (getCharacterAspectRatio()!=aspectRatio)
{
getOrCreateStyle()->setWidthRatio(aspectRatio);
}
setCharacterSize(height);
}
void TextBase::setMaximumWidth(float maximumWidth)
{
_maximumWidth = maximumWidth;
computeGlyphRepresentation();
}
void TextBase::setMaximumHeight(float maximumHeight)
{
_maximumHeight = maximumHeight;
computeGlyphRepresentation();
}
void TextBase::setLineSpacing(float lineSpacing)
{
_lineSpacing = lineSpacing;
computeGlyphRepresentation();
}
void TextBase::setText(const String& text)
{
if (_text==text) return;
_text = text;
computeGlyphRepresentation();
}
void TextBase::setText(const std::string& text)
{
setText(String(text));
}
void TextBase::setText(const std::string& text,String::Encoding encoding)
{
setText(String(text,encoding));
}
void TextBase::setText(const wchar_t* text)
{
setText(String(text));
}
void TextBase::setPosition(const osg::Vec3& pos)
{
if (_position==pos) return;
_position = pos;
computePositions();
}
void TextBase::setAlignment(AlignmentType alignment)
{
if (_alignment==alignment) return;
_alignment = alignment;
computeGlyphRepresentation();
}
void TextBase::setAxisAlignment(AxisAlignment axis)
{
_axisAlignment = axis;
switch(axis)
{
case XZ_PLANE:
setAutoRotateToScreen(false);
setRotation(osg::Quat(osg::inDegrees(90.0f),osg::Vec3(1.0f,0.0f,0.0f)));
break;
case REVERSED_XZ_PLANE:
setAutoRotateToScreen(false);
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:
setAutoRotateToScreen(false);
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:
setAutoRotateToScreen(false);
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:
setAutoRotateToScreen(false);
setRotation(osg::Quat()); // nop - already on XY plane.
break;
case REVERSED_XY_PLANE:
setAutoRotateToScreen(false);
setRotation(osg::Quat(osg::inDegrees(180.0f),osg::Vec3(0.0f,1.0f,0.0f)));
break;
case SCREEN:
setAutoRotateToScreen(true);
setRotation(osg::Quat()); // nop - already on XY plane.
break;
default: break;
}
}
void TextBase::setRotation(const osg::Quat& quat)
{
_rotation = quat;
computePositions();
}
void TextBase::setAutoRotateToScreen(bool autoRotateToScreen)
{
if (_autoRotateToScreen==autoRotateToScreen) return;
_autoRotateToScreen = autoRotateToScreen;
computePositions();
}
void TextBase::setLayout(Layout layout)
{
if (_layout==layout) return;
_layout = layout;
computeGlyphRepresentation();
}
void TextBase::setDrawMode(unsigned int mode)
{
if (_drawMode==mode) return;
_drawMode=mode;
}
void TextBase::setBoundingBoxMargin(float margin)
{
if (_textBBMargin == margin)
return;
_textBBMargin = margin;
computeGlyphRepresentation();
}
osg::BoundingBox TextBase::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.
#if 1
// so fallback to estimating the bounding box size by assuming a scale of 1
// but might cause problems due to small feature culling...
osg::Matrix matrix;
matrix.makeTranslate(_position);
bbox.expandBy(osg::Vec3(_textBB.xMin(),_textBB.yMin(),_textBB.zMin())*matrix);
bbox.expandBy(osg::Vec3(_textBB.xMax(),_textBB.yMax(),_textBB.zMax())*matrix);
#endif
}
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.yMax(),_textBB.zMax())*matrix);
}
}
}
return bbox;
}
void TextBase::computePositions()
{
unsigned int size = osg::maximum(osg::DisplaySettings::instance()->getMaxNumberOfGraphicsContexts(),_autoTransformCache.size());
// FIXME: OPTIMIZE: This would be one of the ideal locations to
// call computeAverageGlyphWidthAndHeight(). 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<size;++i)
{
computePositions(i);
}
}
void TextBase::setThreadSafeRefUnref(bool threadSafe)
{
Drawable::setThreadSafeRefUnref(threadSafe);
}
void TextBase::resizeGLObjectBuffers(unsigned int maxSize)
{
Drawable::resizeGLObjectBuffers(maxSize);
_autoTransformCache.resize(maxSize);
}
void TextBase::releaseGLObjects(osg::State* state) const
{
Drawable::releaseGLObjects(state);
}
void TextBase::positionCursor(const osg::Vec2 & endOfLine_coords, osg::Vec2 & cursor, unsigned int linelength)
{
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;
}
}
}
AutoTransform
Description: Binary data
_______________________________________________ osg-submissions mailing list [email protected] http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
