=========================
Author: Thomas Geymayer <admin@tomprogs.at>
-Revision: 2012/05/04
+Revision: 2012/05/18
Introduction
------------
A new canvas can be instantiated by creating a node /canvas/texture[<INDEX>]
with at least the following children:
- <size-x type="int"> The width of the underlying texture
- <size-y type="int"> The height of the underlying texture
+ <size n="0" type="int"> The width of the underlying texture
+ <size n="1" type="int"> The height of the underlying texture
- <view-width type="int"> The width of the canvas
- <view-height type="int"> The height of the canvas
+ <view n="0" type="int"> The width of the canvas
+ <view n="1" type="int"> The height of the canvas
The dimensions of the canvas are needed to be able to use textures with
different resolutions but use the same units for rendering to the canvas.
<red type="float">
<green type="float">
<blue type="float">
+ <alpha type="float">
</NAME>
* Text:
2. aircraft-dir
3. $FG_DATA/Fonts
4. Default osg font paths
- <size type="float"> The font size (default: 32)
+ <character-size type="float"> The font size (default: 32)
+ <character-aspect-ratio type="float"> Ratio between character height and width
+ (default: 1)
<tf> A 3x3 transformation matrix specified by 6 values
(child elements <a>, ..., <f>) See
http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
<canvas>
<texture>
- <size-x type="int">384</size-x>
- <size-y type="int">512</size-y>
- <view-width type="int">768</view-width>
- <view-height type="int">1024</view-height>
+ <size n="0" type="int">384</size-x>
+ <size n="1" type="int">512</size-y>
+ <view n="0" type="int">768</view-width>
+ <view n="1" type="int">1024</view-height>
<mipmapping type="bool">false</mipmapping>
<coverage-samples type="int">0</coverage-samples>
<color-samples type="int">0</color-samples>
+ <color-background>
+ <red type="float">0</red>
+ <green type="float">0.02</green>
+ <blue type="float">0</blue>
+ <alpha type="float">1</alpha>
+ </color-background>
<group>
<text>
<text type="string">TEST MESSAGE</text>
<font type="string">helvetica_bold.txf</font>
+ <character-size type="float">40</character-size>
<tf>
<!-- Translate (18|50) -->
<tx>18</tx>
elements/element.cxx
elements/group.cxx
elements/text.cxx
+ property_helper.cxx
)
set(HEADERS
elements/element.hxx
elements/group.hxx
elements/text.hxx
+ property_helper.hxx
)
flightgear_component(Canvas "${SOURCES}" "${HEADERS}")
#include "canvas.hxx"
#include "elements/group.hxx"
+#include <Canvas/property_helper.hxx>
#include <osg/Camera>
#include <osg/Geode>
#include <iostream>
-//#include <Main/globals.hxx>
-//#include <Viewer/renderer.hxx>
-
//------------------------------------------------------------------------------
/**
* Callback used to disable/enable rendering to the texture if it is not
_view_width(-1),
_view_height(-1),
_status(0),
- _node(0),
- _sampling_dirty(false)
+ _sampling_dirty(false),
+ _color_dirty(true),
+ _node(0)
{
setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y);
//------------------------------------------------------------------------------
Canvas::~Canvas()
{
+ clearPlacements();
+ unbind();
+ _node = 0;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void Canvas::reset(SGPropertyNode* node)
{
- if( _node )
- {
- _node->untie("size[0]");
- _node->untie("size[1]");
- _node->untie("view[0]");
- _node->untie("view[1]");
- _node->untie("status");
- _node->untie("status-msg");
- _node->removeChangeListener(this);
- _node = 0;
- }
+ if( node )
+ SG_LOG
+ (
+ SG_GL,
+ SG_INFO,
+ "Canvas::reset() texture[" << node->getIndex() << "]"
+ );
+
+ unbind();
+ _node = node;
setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y);
- if( node )
+ if( _node )
{
- _node = node;
_node->tie
(
"size[0]",
SGRawValueMethods<Canvas, const char*>(*this, &Canvas::getStatusMsg)
);
_node->addChangeListener(this);
+
+ canvas::linkColorNodes
+ (
+ "color-background",
+ _node,
+ _color_background,
+ osg::Vec4f(0,0,0,1)
+ );
}
}
);
_sampling_dirty = false;
}
+ if( _color_dirty )
+ {
+ _texture.getCamera()->setClearColor
+ (
+ osg::Vec4( _color_background[0]->getFloatValue(),
+ _color_background[1]->getFloatValue(),
+ _color_background[2]->getFloatValue(),
+ _color_background[3]->getFloatValue() )
+ );
+ _color_dirty = false;
+ }
while( !_dirty_placements.empty() )
{
{
_dirty_placements.push_back(child);
}
-// else
-// std::cout << "Canvas::childAdded: " << child->getPath() << std::endl;
}
//------------------------------------------------------------------------------
if( child->getNameString() == "placement" )
clearPlacements(child->getIndex());
- else
- std::cout << "Canvas::childRemoved: " << child->getPath() << std::endl;
}
//----------------------------------------------------------------------------
void Canvas::valueChanged(SGPropertyNode * node)
{
- if( node->getParent()->getParent() == _node
- && node->getParent()->getNameString() == "placement" )
+ if( node->getParent()->getParent() == _node )
{
- // prevent double updates...
- for( size_t i = 0; i < _dirty_placements.size(); ++i )
+ if( !_color_background.empty()
+ && _color_background[0]->getParent() == node->getParent() )
{
- if( node->getParent() == _dirty_placements[i] )
- return;
+ _color_dirty = true;
}
+ else if( node->getParent()->getNameString() == "placement" )
+ {
+ // prevent double updates...
+ for( size_t i = 0; i < _dirty_placements.size(); ++i )
+ {
+ if( node->getParent() == _dirty_placements[i] )
+ return;
+ }
- _dirty_placements.push_back(node->getParent());
+ _dirty_placements.push_back(node->getParent());
+ }
}
else if( node->getParent() == _node )
{
parent->removeChild(group);
}
}
+
+//------------------------------------------------------------------------------
+void Canvas::clearPlacements()
+{
+ for(size_t i = 0; i < _placements.size(); ++i)
+ clearPlacements(i);
+ _placements.clear();
+}
+
+//------------------------------------------------------------------------------
+void Canvas::unbind()
+{
+ if( !_node )
+ return;
+
+ _node->untie("size[0]");
+ _node->untie("size[1]");
+ _node->untie("view[0]");
+ _node->untie("view[1]");
+ _node->untie("status");
+ _node->untie("status-msg");
+ _node->removeChangeListener(this);
+}
private:
+ Canvas(const Canvas&); // = delete;
+ Canvas& operator=(const Canvas&); // = delete;
+
int _size_x,
_size_y,
_view_width,
int _status;
std::string _status_msg;
- FGODGauge _texture;
- SGPropertyNode *_node;
+ bool _sampling_dirty,
+ _color_dirty;
+
+ FGODGauge _texture;
- bool _sampling_dirty;
+ SGPropertyNode_ptr _node;
+ std::vector<SGPropertyNode_ptr> _color_background;
osg::ref_ptr<osg::NodeCallback> _camera_callback;
osg::ref_ptr<osg::NodeCallback> _cull_callback;
void setStatusFlags(unsigned int flags, bool set = true);
void clearPlacements(int index);
+ void clearPlacements();
+
+ void unbind();
};
#endif /* CANVAS_HXX_ */
void CanvasMgr::update(double delta_time_sec)
{
for( size_t i = 0; i < _canvases.size(); ++i )
- _canvases[i].update(delta_time_sec);
+ if( _canvases[i] )
+ _canvases[i]->update(delta_time_sec);
}
//------------------------------------------------------------------------------
if( child->getNameString() == "texture" )
textureAdded(child);
- else
- std::cout << "CanvasMgr::childAdded: " << child->getPath() << std::endl;
}
//------------------------------------------------------------------------------
if( parent != _props )
return;
- std::cout << "CanvasMgr::childRemoved: " << child->getPath() << std::endl;
+ if( child->getNameString() == "texture" )
+ {
+ size_t index = child->getIndex();
+
+ if( index >= _canvases.size() )
+ SG_LOG(SG_GL, SG_WARN, "can't removed unknown texture[" << index << "]!");
+ else
+ // remove the canvas...
+ _canvases[index].reset();
+ }
}
//------------------------------------------------------------------------------
{
if( index > _canvases.size() )
SG_LOG(SG_GL, SG_WARN, "Skipping unused texture slot(s)!");
- SG_LOG(SG_GL, SG_INFO, "Add new texture[" << index << "]");
_canvases.resize(index + 1);
- _canvases[index];
}
else
{
SG_LOG(SG_GL, SG_WARN, "texture[" << index << "] already exists!");
}
- _canvases[index].reset(node);
+ _canvases[index].reset( new Canvas() );
+ _canvases[index]->reset(node);
}
//------------------------------------------------------------------------------
for( int i = 0; i < node->nChildren(); ++i )
triggerChangeRecursive( node->getChild(i) );
}
-
-//------------------------------------------------------------------------------
-template<class T>
-T CanvasMgr::getParam(const SGPropertyNode* node, const char* prop)
-{
- const SGPropertyNode* child = node->getChild(prop);
- if( !child )
- throw std::runtime_error(std::string("Missing property ") + prop);
- return getValue<T>(child);
-}
#include <vector>
class Canvas;
+typedef boost::shared_ptr<Canvas> CanvasPtr;
class CanvasMgr:
public SGSubsystem,
SGPropertyNode_ptr _props;
/** The actual canvases */
- std::vector<Canvas> _canvases;
+ std::vector<CanvasPtr> _canvases;
void textureAdded(SGPropertyNode* node);
*/
void triggerChangeRecursive(SGPropertyNode* node);
- /**
- * Get the value of a property or throw an exception if it doesn't exist.
- */
- template<class T>
- T getParam(const SGPropertyNode* node, const char* prop);
-
};
#endif /* CANVAS_MGR_H_ */
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "element.hxx"
-#include <cassert>
+#include <Canvas/property_helper.hxx>
#include <osg/Drawable>
+#include <cassert>
+#include <cstring>
+
namespace canvas
{
const std::string NAME_TRANSFORM = "tf";
}
- //----------------------------------------------------------------------------
- SGPropertyNode* Element::getPropertyNode()
- {
- return _node;
- }
-
//----------------------------------------------------------------------------
void Element::update(double dt)
{
colorChanged( osg::Vec4( _color[0]->getFloatValue(),
_color[1]->getFloatValue(),
_color[2]->getFloatValue(),
- 1 ) );
+ _color[3]->getFloatValue() ) );
_attributes_dirty &= ~COLOR;
}
colorFillChanged( osg::Vec4( _color_fill[0]->getFloatValue(),
_color_fill[1]->getFloatValue(),
_color_fill[2]->getFloatValue(),
- 1 ) );
+ _color_fill[3]->getFloatValue() ) );
_attributes_dirty &= ~COLOR_FILL;
}
- if( _drawable && (_attributes_dirty & BOUNDING_BOX) )
+ if( !_bounding_box.empty() )
{
- _bounding_box[0]->setFloatValue(_drawable->getBound()._min.x());
- _bounding_box[1]->setFloatValue(_drawable->getBound()._min.y());
- _bounding_box[2]->setFloatValue(_drawable->getBound()._max.x());
- _bounding_box[3]->setFloatValue(_drawable->getBound()._max.y());
+ assert( _drawable );
- _attributes_dirty &= ~BOUNDING_BOX;
+ const osg::BoundingBox& bb = _drawable->getBound();
+ _bounding_box[0]->setFloatValue(bb._min.x());
+ _bounding_box[1]->setFloatValue(bb._min.y());
+ _bounding_box[2]->setFloatValue(bb._max.x());
+ _bounding_box[3]->setFloatValue(bb._max.y());
}
}
}
//----------------------------------------------------------------------------
- Element::Element(SGPropertyNode* node, uint32_t attributes_used):
- _node( node ),
- _drawable( 0 ),
+ Element::Element(SGPropertyNode_ptr node, uint32_t attributes_used):
_attributes_used( attributes_used ),
- _attributes_dirty( 0 ),
+ _attributes_dirty( attributes_used ),
_transform_dirty( false ),
- _transform( new osg::MatrixTransform )
+ _transform( new osg::MatrixTransform ),
+ _node( node ),
+ _drawable( 0 )
{
assert( _node );
_node->addChangeListener(this);
if( _attributes_used & COLOR )
- linkColorNodes("color", _color, osg::Vec4f(0,1,0,1));
+ linkColorNodes("color", _node, _color, osg::Vec4f(0,1,0,1));
+
if( _attributes_used & COLOR_FILL )
- linkColorNodes("color-fill", _color_fill);
- if( _attributes_used & BOUNDING_BOX )
- {
- SGPropertyNode* bb = _node->getChild("bounding-box", 0, true);
- _bounding_box[0] = bb->getChild("x-min", 0, true);
- _bounding_box[1] = bb->getChild("y-min", 0, true);
- _bounding_box[2] = bb->getChild("x-max", 0, true);
- _bounding_box[3] = bb->getChild("y-max", 0, true);
- }
+ linkColorNodes("color-fill", _node, _color_fill);
SG_LOG
(
}
//----------------------------------------------------------------------------
- void Element::linkColorNodes( const char* name,
- SGPropertyNode** nodes,
- const osg::Vec4& def )
+ void Element::setDrawable( osg::Drawable* drawable )
{
- // Don't tie to allow the usage of aliases
- SGPropertyNode* color = _node->getChild(name, 0, true);
+ _drawable = drawable;
+ assert( _drawable );
- static const char* color_names[] = {"red", "green", "blue"};
- for( size_t i = 0; i < sizeof(color_names)/sizeof(color_names[0]); ++i )
+ if( _attributes_used & BOUNDING_BOX )
{
- color->setFloatValue
- (
- color_names[i],
- color->getFloatValue(color_names[i], def[i])
- );
- nodes[i] = color->getChild(color_names[i]);
+ SGPropertyNode* bb_node = _node->getChild("bounding-box", 0, true);
+ _bounding_box.resize(4);
+ _bounding_box[0] = bb_node->getChild("min-x", 0, true);
+ _bounding_box[1] = bb_node->getChild("min-y", 0, true);
+ _bounding_box[2] = bb_node->getChild("max-x", 0, true);
+ _bounding_box[3] = bb_node->getChild("max-y", 0, true);
}
}
{
if( parent->getNameString() == NAME_TRANSFORM )
_transform_dirty = true;
- else if( parent->getNameString() == NAME_COLOR )
+ else if( !_color.empty() && _color[0]->getParent() == parent )
_attributes_dirty |= COLOR;
- else if( parent->getNameString() == NAME_COLOR_FILL )
+ else if( !_color_fill.empty() && _color_fill[0]->getParent() == parent )
_attributes_dirty |= COLOR_FILL;
}
- else
- childChanged(child);
+ else if( parent == _node )
+ {
+ if( child->getNameString() == "update" )
+ update(0);
+ else
+ childChanged(child);
+ }
}
} // namespace canvas
{
public:
virtual ~Element() = 0;
- SGPropertyNode* getPropertyNode();
/**
* Called every frame to update internal state
{
COLOR = 0x0001,
COLOR_FILL = 0x0002,
- BOUNDING_BOX = 0x0004
+ BOUNDING_BOX = 0x0004,
+ LAST_ATTRIBUTE = BOUNDING_BOX
};
enum TransformType
TT_SCALE
};
- SGPropertyNode *_node;
- osg::Drawable *_drawable;
-
uint32_t _attributes_used;
uint32_t _attributes_dirty;
osg::ref_ptr<osg::MatrixTransform> _transform;
std::vector<TransformType> _transform_types;
- SGPropertyNode *_bounding_box[4]; ///<! x-min, y-min, x-max, y-max
- SGPropertyNode *_color[3];
- SGPropertyNode *_color_fill[3];
+ SGPropertyNode_ptr _node;
+ std::vector<SGPropertyNode_ptr> _color,
+ _color_fill;
+ std::vector<SGPropertyNode_ptr> _bounding_box;
- Element(SGPropertyNode* node, uint32_t attributes_used = 0);
+ Element(SGPropertyNode_ptr node, uint32_t attributes_used = 0);
virtual void childAdded(SGPropertyNode * child) {}
virtual void childRemoved(SGPropertyNode * child){}
virtual void colorChanged(const osg::Vec4& color) {}
virtual void colorFillChanged(const osg::Vec4& color){}
- void linkColorNodes( const char* name,
- SGPropertyNode** nodes,
- const osg::Vec4& def = osg::Vec4(1,1,0,1) );
+ void setDrawable( osg::Drawable* drawable );
private:
+
+ osg::Drawable *_drawable;
+
Element(const Element&);// = delete
virtual void childAdded( SGPropertyNode * parent,
{
//----------------------------------------------------------------------------
- Group::Group(SGPropertyNode* node):
+ Group::Group(SGPropertyNode_ptr node):
Element(node)
{
void Group::update(double dt)
{
for( size_t i = 0; i < _children.size(); ++i )
- _children[i]->update(dt);
+ {
+ if( _children[i] )
+ _children[i]->update(dt);
+ }
Element::update(dt);
}
//----------------------------------------------------------------------------
void Group::childAdded(SGPropertyNode* child)
{
+ boost::shared_ptr<Element> element;
+
if( child->getNameString() == "text" )
- {
- _children.push_back( boost::shared_ptr<Element>(new Text(child)) );
- _transform->addChild( _children.back()->getMatrixTransform() );
- }
+ element.reset( new Text(child) );
+ else if( child->getNameString() == "group" )
+ element.reset( new Group(child) );
else
- std::cout << "New unknown child: " << child->getDisplayName() << std::endl;
+ SG_LOG
+ (
+ SG_GL,
+ SG_WARN,
+ "canvas::Group unknown child: " << child->getDisplayName()
+ );
+
+ if( !element )
+ return;
+
+ // Add to osg scene graph...
+ _transform->addChild( element->getMatrixTransform() );
+
+ // ...and build up canvas hierarchy
+ size_t index = child->getIndex();
+
+ if( index >= _children.size() )
+ _children.resize(index + 1);
+
+ _children[index] = element;
}
//----------------------------------------------------------------------------
void Group::childRemoved(SGPropertyNode* child)
{
+ if( child->getNameString() == "text"
+ || child->getNameString() == "group" )
+ {
+ size_t index = child->getIndex();
+ if( index >= _children.size() )
+ SG_LOG
+ (
+ SG_GL,
+ SG_WARN,
+ "can't removed unknown child " << child->getDisplayName()
+ );
+ else
+ {
+ boost::shared_ptr<Element>& element = _children[index];
+ if( element )
+ _transform->removeChild(element->getMatrixTransform());
+ element.reset();
+ }
+ }
}
} // namespace canvas
namespace canvas
{
+ typedef boost::shared_ptr<Element> ElementPtr;
+
class Group:
public Element
{
public:
- Group(SGPropertyNode* node);
+ Group(SGPropertyNode_ptr node);
virtual ~Group();
virtual void update(double dt);
protected:
- std::vector<boost::shared_ptr<Element> > _children;
+ std::vector<ElementPtr> _children;
virtual void childAdded(SGPropertyNode * child);
virtual void childRemoved(SGPropertyNode * child);
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "text.hxx"
-#include <osgText/Text>
+#include <Canvas/property_helper.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
+#include <osgText/Text>
+
namespace canvas
{
//----------------------------------------------------------------------------
- Text::Text(SGPropertyNode* node):
+ Text::Text(SGPropertyNode_ptr node):
Element(node, COLOR | COLOR_FILL | BOUNDING_BOX),
- _text( new osgText::Text )
+ _text( new osgText::Text ),
+ _font_size( 0 ),
+ _font_aspect( 0 )
{
- _drawable = _text;
- _text->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
+ setDrawable(_text);
+ _text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
+ _text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
+ _text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
- // font size and property node
- float character_size = _node->getFloatValue("size", 32);
- _text->setCharacterSize( character_size );
- _node->setFloatValue("size", character_size);
+ _font_size = getChildDefault<float>(_node, "character-size", 32);
+ _font_aspect = getChildDefault<float>(_node, "character-aspect-ratio", 1);
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(_text);
//----------------------------------------------------------------------------
Text::~Text()
{
+ if( _node )
+ {
+ _node->untie("alignment");
+ _node->untie("padding");
+ _node->untie("draw-mode");
+ }
+ _node = 0;
+ }
+
+ //----------------------------------------------------------------------------
+ void Text::update(double dt)
+ {
+ Element::update(dt);
+
+ if( _attributes_dirty & FONT_SIZE )
+ {
+ _text->setCharacterSize
+ (
+ _font_size->getFloatValue(),
+ _font_aspect->getFloatValue()
+ );
+ _attributes_dirty &= ~FONT_SIZE;
+ }
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
void Text::childChanged(SGPropertyNode* child)
{
- if( child->getNameString() == "text" )
- _text->setText(child->getStringValue());
- else if( child->getNameString() == "size" )
- _text->setCharacterSize( child->getFloatValue() );
+ if( _font_size == child || _font_aspect == child )
+ _attributes_dirty |= FONT_SIZE;
+ else if( child->getNameString() == "text" )
+ _text->setText( child->getStringValue() );
else if( child->getNameString() == "font" )
setFont( child->getStringValue() );
- else
- return;
-
- _attributes_dirty |= BOUNDING_BOX;
}
//----------------------------------------------------------------------------
public Element
{
public:
- Text(SGPropertyNode* node);
+ Text(SGPropertyNode_ptr node);
virtual ~Text();
+ virtual void update(double dt);
+
void setFont(const char* name);
void setAlignment(const char* align);
const char* getAlignment() const;
protected:
+
+ enum TextAttributes
+ {
+ FONT_SIZE = LAST_ATTRIBUTE << 1, // Font size and aspect ration
+ };
+
osg::ref_ptr<osgText::Text> _text;
+ SGPropertyNode_ptr _font_size,
+ _font_aspect;
+
virtual void childChanged(SGPropertyNode * child);
virtual void colorChanged(const osg::Vec4& color);
virtual void colorFillChanged(const osg::Vec4& color);
--- /dev/null
+// Some helper functions for accessing the property tree
+//
+// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program 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 GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+#include "property_helper.hxx"
+#include <cassert>
+
+namespace canvas
+{
+ //----------------------------------------------------------------------------
+ void linkColorNodes( const char* name,
+ SGPropertyNode* parent,
+ std::vector<SGPropertyNode_ptr>& nodes,
+ const osg::Vec4& def )
+ {
+ static const char* channels[] = {"red", "green", "blue", "alpha"};
+ static const size_t num_channels = sizeof(channels)/sizeof(channels[0]);
+
+ assert(name);
+ assert(parent);
+
+ // Don't tie to allow the usage of aliases
+ SGPropertyNode_ptr color = parent->getChild(name, 0, true);
+
+ // We need to be carefull do not get any unitialized nodes or null pointers
+ // because while creating the node a valueChanged event will be triggered.
+ nodes.clear();
+ nodes.reserve(num_channels);
+
+ for( size_t i = 0; i < num_channels; ++i )
+ nodes.push_back( getChildDefault(color, channels[i], def[i]) );
+ }
+}
--- /dev/null
+// Some helper functions for accessing the property tree
+//
+// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program 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 GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+#ifndef PROPERTY_HELPER_HXX_
+#define PROPERTY_HELPER_HXX_
+
+#include <simgear/props/props.hxx>
+#include <osg/Vec4>
+
+namespace canvas
+{
+
+ /**
+ * Get property node with default value
+ */
+ template<typename T>
+ SGPropertyNode* getChildDefault( SGPropertyNode* parent,
+ const char* name,
+ T def_val )
+ {
+ SGPropertyNode* node = parent->getNode(name);
+ if( node )
+ // also set value for existing nodes to enforce type...
+ def_val = getValue<T>(node);
+ else
+ node = parent->getChild(name, 0, true);
+
+ setValue(node, def_val);
+ return node;
+ }
+
+ /**
+ * @param name Name of color node
+ * @param parent Parent for color channel nodes
+ * @param nodes Vector to push color nodes into
+ * @param def Default color
+ *
+ * <name>
+ * <red type="float">def[0] or existing value</red>
+ * <green type="float">def[1] or existing value</green>
+ * <blue type="float">def[2] or existing value</blue>
+ * <alpha type="float">def[3] or existing value</alpha>
+ * </name>
+ */
+ void linkColorNodes( const char* name,
+ SGPropertyNode* parent,
+ std::vector<SGPropertyNode_ptr>& nodes,
+ const osg::Vec4& def = osg::Vec4(0,0,0,0) );
+
+} // namespace canvas
+
+#endif /* PROPERTY_HELPER_HXX_ */