1 // SGText.cxx - Manage text in the scene graph
2 // Copyright (C) 2009 Torsten Dreyer Torsten (_at_) t3r *dot* de
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License as
6 // published by the Free Software Foundation; either version 2 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 # include <simgear_config.h>
27 #include <simgear/math/SGMath.hxx>
28 #include <simgear/misc/sg_path.hxx>
29 #include <simgear/misc/strutils.hxx>
32 #include <osg/MatrixTransform>
33 #include <osgText/Text>
34 #include <osgText/Font>
38 class SGText::UpdateCallback : public osg::NodeCallback {
40 UpdateCallback( osgText::Text * aText, SGConstPropertyNode_ptr aProperty, double aScale, double aOffset, bool aTruncate, bool aNumeric, const char * aFormat ) :
42 property( aProperty ),
45 truncate( aTruncate ),
47 format( simgear::strutils::sanitizePrintfFormat( aFormat ) )
49 if( format.empty() ) {
50 if( numeric ) format = "%f";
56 virtual void operator()(osg::Node * node, osg::NodeVisitor *nv );
58 SGConstPropertyNode_ptr property;
66 void SGText::UpdateCallback::operator()(osg::Node * node, osg::NodeVisitor *nv )
69 // hopefully the users never specifies bad formats here
70 // this should better be something more robust
73 double d = property->getDoubleValue() * scale + offset;
74 if (truncate) d = (d < 0) ? -floor(-d) : floor(d);
75 snprintf( buf, sizeof(buf)-1, format.c_str(), d );
77 snprintf( buf, sizeof(buf)-1, format.c_str(), property->getStringValue() );
79 if( text->getText().createUTF8EncodedString().compare( buf ) ) {
80 // be lazy and set the text only if the property has changed.
81 // update() computes the glyph representation which looks
82 // more expensive than a the above string compare.
89 osg::Node * SGText::appendText(const SGPropertyNode* configNode,
90 SGPropertyNode* modelRoot, const osgDB::Options* options)
92 SGConstPropertyNode_ptr p;
94 osgText::Text * text = new osgText::Text();
95 osg::Geode * g = new osg::Geode;
96 g->addDrawable( text );
98 SGPath path("Fonts" );
99 path.append( configNode->getStringValue( "font", "Helvetica" ));
100 text->setFont( path.str() );
102 text->setCharacterSize(configNode->getDoubleValue("character-size", 1.0 ),
103 configNode->getDoubleValue("character-aspect-ratio", 1.0 ));
105 if( (p = configNode->getNode( "font-resolution" )) != NULL )
106 text->setFontResolution( p->getIntValue( "width", 32 ), p->getIntValue( "height", 32 ) );
108 if( (p = configNode->getNode( "kerning" )) != NULL ) {
109 string kerning = p->getStringValue();
110 if( kerning.compare( "default" ) == 0 ) {
111 text->setKerningType( osgText::KERNING_DEFAULT );
112 } else if( kerning.compare( "unfitted" ) == 0 ) {
113 text->setKerningType( osgText::KERNING_UNFITTED );
114 } else if( kerning.compare( "none" ) == 0 ) {
115 text->setKerningType( osgText::KERNING_NONE );
117 SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown kerning'" << kerning << "'." );
121 if( ( p = configNode->getNode( "axis-alignment" )) != NULL ) {
122 string axisAlignment = p->getStringValue();
123 if( axisAlignment.compare( "xy-plane" ) == 0 ) {
124 text->setAxisAlignment( osgText::Text::XY_PLANE );
125 } else if( axisAlignment.compare( "reversed-xy-plane" ) == 0 ) {
126 text->setAxisAlignment( osgText::Text::REVERSED_XY_PLANE );
127 } else if( axisAlignment.compare( "xz-plane" ) == 0 ) {
128 text->setAxisAlignment( osgText::Text::XZ_PLANE );
129 } else if( axisAlignment.compare( "reversed-xz-plane" ) == 0 ) {
130 text->setAxisAlignment( osgText::Text::REVERSED_XZ_PLANE );
131 } else if( axisAlignment.compare( "yz-plane" ) == 0 ) {
132 text->setAxisAlignment( osgText::Text::YZ_PLANE );
133 } else if( axisAlignment.compare( "reversed-yz-plane" ) == 0 ) {
134 text->setAxisAlignment( osgText::Text::REVERSED_YZ_PLANE );
135 } else if( axisAlignment.compare( "screen" ) == 0 ) {
136 text->setAxisAlignment( osgText::Text::SCREEN );
138 SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown axis-alignment'" << axisAlignment << "'." );
142 unsigned drawMode = osgText::Text::TEXT;
143 if( (p = configNode->getNode( "draw-text" )) != NULL && p->getBoolValue() == false )
144 drawMode &= ~ osgText::Text::TEXT;
146 if( (p = configNode->getNode( "draw-alignment" )) != NULL && p->getBoolValue() == true )
147 drawMode |= osgText::Text::ALIGNMENT;
149 if( (p = configNode->getNode( "draw-boundingbox" )) != NULL && p->getBoolValue() == true )
150 drawMode |= osgText::Text::BOUNDINGBOX;
152 text->setDrawMode( drawMode );
154 if( (p = configNode->getNode( "alignment" )) != NULL ) {
155 string alignment = p->getStringValue();
156 if( alignment.compare( "left-top" ) == 0 ) {
157 text->setAlignment( osgText::Text::LEFT_TOP );
158 } else if( alignment.compare( "left-center" ) == 0 ) {
159 text->setAlignment( osgText::Text::LEFT_CENTER );
160 } else if( alignment.compare( "left-bottom" ) == 0 ) {
161 text->setAlignment( osgText::Text::LEFT_BOTTOM );
162 } else if( alignment.compare( "center-top" ) == 0 ) {
163 text->setAlignment( osgText::Text::CENTER_TOP );
164 } else if( alignment.compare( "center-center" ) == 0 ) {
165 text->setAlignment( osgText::Text::CENTER_CENTER );
166 } else if( alignment.compare( "center-bottom" ) == 0 ) {
167 text->setAlignment( osgText::Text::CENTER_BOTTOM );
168 } else if( alignment.compare( "right-top" ) == 0 ) {
169 text->setAlignment( osgText::Text::RIGHT_TOP );
170 } else if( alignment.compare( "right-center" ) == 0 ) {
171 text->setAlignment( osgText::Text::RIGHT_CENTER );
172 } else if( alignment.compare( "right-bottom" ) == 0 ) {
173 text->setAlignment( osgText::Text::RIGHT_BOTTOM );
174 } else if( alignment.compare( "left-baseline" ) == 0 ) {
175 text->setAlignment( osgText::Text::LEFT_BASE_LINE );
176 } else if( alignment.compare( "center-baseline" ) == 0 ) {
177 text->setAlignment( osgText::Text::CENTER_BASE_LINE );
178 } else if( alignment.compare( "right-baseline" ) == 0 ) {
179 text->setAlignment( osgText::Text::RIGHT_BASE_LINE );
180 } else if( alignment.compare( "baseline" ) == 0 ) {
181 text->setAlignment( osgText::Text::BASE_LINE );
183 SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown text-alignment '" << alignment <<"'." );
187 if( (p = configNode->getNode( "layout" )) != NULL ) {
188 string layout = p->getStringValue();
189 if( layout.compare( "left-to-right" ) == 0 ) {
190 text->setLayout( osgText::Text::LEFT_TO_RIGHT );
191 } else if( layout.compare( "right-to-left" ) == 0 ) {
192 text->setLayout( osgText::Text::RIGHT_TO_LEFT );
193 } else if( layout.compare( "vertical" ) == 0 ) {
194 text->setLayout( osgText::Text::VERTICAL );
196 SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown layout '" << layout <<"'." );
200 if( (p = configNode->getNode( "max-width" )) != NULL )
201 text->setMaximumWidth( p->getDoubleValue() );
203 if( (p = configNode->getNode( "max-height" )) != NULL )
204 text->setMaximumHeight( p->getDoubleValue() );
206 // FIXME: Should we support <chunks><chunk/><chunk/></chunks> here?
207 string type = configNode->getStringValue( "type", "literal" );
208 if( type == "literal" ) {
209 text->setText( configNode->getStringValue( "text", "" ) );
211 SGConstPropertyNode_ptr property = modelRoot->getNode( configNode->getStringValue( "property", "foo" ), true );
212 const char * format = configNode->getStringValue( "format", "" );
213 double scale = configNode->getDoubleValue( "scale", 1.0 );
214 double offset = configNode->getDoubleValue( "offset", 0.0 );
215 bool truncate = configNode->getBoolValue( "truncate", false );
217 if( (p = configNode->getNode( "property")) != NULL ) {
218 p = modelRoot->getNode( p->getStringValue(), true );
219 UpdateCallback * uc = new UpdateCallback( text, property, scale, offset, truncate, type == "number-value", format );
220 g->setUpdateCallback( uc );
224 osg::Node * reply = NULL;
225 if( (p = configNode->getNode( "offsets")) == NULL ) {
228 // Set up the alignment node ("stolen" from animation.cxx)
229 // XXX Order of rotations is probably not correct.
230 osg::MatrixTransform *align = new osg::MatrixTransform;
231 osg::Matrix res_matrix;
232 res_matrix.makeRotate(
233 p->getFloatValue("pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
235 p->getFloatValue("roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
237 p->getFloatValue("heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
241 tmat.makeTranslate(configNode->getFloatValue("offsets/x-m", 0.0),
242 configNode->getFloatValue("offsets/y-m", 0.0),
243 configNode->getFloatValue("offsets/z-m", 0.0));
245 align->setMatrix(res_matrix * tmat);
246 align->addChild( g );
250 if( (p = configNode->getNode( "name" )) != NULL )
251 reply->setName(p->getStringValue());
253 reply->setName("text");