diff --git a/docs-mini/README.canvas b/docs-mini/README.canvas new file mode 100644 index 000000000..670f1d17d --- /dev/null +++ b/docs-mini/README.canvas @@ -0,0 +1,157 @@ +Canvas - A 2D Drawing API +========================= + +Author: Thomas Geymayer +Revision: 2012/05/04 + +Introduction +------------ + +With the increasing complexity of (glass) cockpits the need for a simple API to +draw on a 2D surface without modifying the C++ core increased heavily in the +last time. The 2D canvas is an effort to satisfy this needs. It is now possible +to create offscreen rendertargets only by using the property tree and placing +them on any 3D object on the aircraft by using certain filter criteria. + +Currently it is only possible to place text on the canvas but 2d shapes (using +OpenVG) are going to follow. + +Creating a canvas +----------------- + +A new canvas can be instantiated by creating a node /canvas/texture[] +with at least the following children: + + The width of the underlying texture + The height of the underlying texture + + The width of the canvas + 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. +Therefore you can choose any texture size with the same canvas size and always +get the same results (apart from resolution dependent artifacts). + +* Filtering: + + Optionally you can enable mipmapping and/or multisampling (Coverage Sampling + Antialiasing): + + Use mipmapping (default: false) + Coverage Samples (default: 0) + Color Samples (default: 0, always + have to be <= coverage-samples) +Drawing +------- + +Drawing to the canvas is accomplished by creating nodes as childs of the +canvas root node. Every shape has to be a child of a node. Currently +only drawing Text is possible: + +* General: + The following parameters are used by multiple elements: + + Color: + A color can be specified by the following subtree (NAME is replaced by + another name depending on the usage of the color) + + + + + + + +* Text: + Create a node and configure with the following properties: + + The text to be displayed + The font to be used (Searched in + 1. aircraft-dir/Fonts + 2. aircraft-dir + 3. $FG_DATA/Fonts + 4. Default osg font paths + The font size (default: 32) + A 3x3 transformation matrix specified by 6 values + (child elements , ..., ) See + http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined + for details. + You can also use shortcuts and use an alternative to + specifying six values: + - Translation: , (ty, ty default to 0) + - Totation: + - Scale: , (sx is required, sy defaults to sx) + Text alignment (default: "left-baseline") One of: + + "left-top" + "left-center" + "left-bottom" + + "center-top" + "center-center" + "center-bottom" + + "right-top" + "right-center" + "right-bottom" + + "left-baseline" + "center-baseline" + "right-baseline" + + "left-bottom-baseline" + "center-bottom-baseline" + "right-bottom-baseline" + A bitwise combination of the following values + 1 (Text - default) + 2 (Boundingbox) + 4 (Filled boundingbox) + 8 (Alignment -> Draw a cross at the position + of the text) + Padding between for the boundingbox (default: 0) + Text color + Fill color (for the bounding box) + +Placement +--------- + +To place the canvas into the scene one ore more elements can be +added to the texture node. By setting at least on of the following nodes +the objects where the canvas texture should be placed on are selected: + + Match objects with the given texture assigned + Match objects with the given name + Match objects with a parent matching the given name + (Not necessarily the direct parent) + +Example +------- + + + + 384 + 512 + 768 + 1024 + false + 0 + 0 + + + TEST MESSAGE + helvetica_bold.txf + + + 18 + 50 + + + + + + EICAS.png + HDD 1 + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ab32ec186..b09b6a348 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,6 +8,7 @@ foreach( mylibfolder Aircraft ATC ATCDCL + Canvas Radio Autopilot Cockpit diff --git a/src/Canvas/CMakeLists.txt b/src/Canvas/CMakeLists.txt new file mode 100644 index 000000000..1dde0d26b --- /dev/null +++ b/src/Canvas/CMakeLists.txt @@ -0,0 +1,19 @@ +include(FlightGearComponent) + +set(SOURCES + canvas.cxx + canvas_mgr.cxx + elements/element.cxx + elements/group.cxx + elements/text.cxx +) + +set(HEADERS + canvas.hxx + canvas_mgr.hxx + elements/element.hxx + elements/group.hxx + elements/text.hxx +) + +flightgear_component(Canvas "${SOURCES}" "${HEADERS}") diff --git a/src/Canvas/canvas.cxx b/src/Canvas/canvas.cxx new file mode 100644 index 000000000..31e82653a --- /dev/null +++ b/src/Canvas/canvas.cxx @@ -0,0 +1,430 @@ +// The canvas for rendering with the 2d api +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 "canvas.hxx" +#include "elements/group.hxx" + +#include +#include +#include + +#include + +//#include
+//#include + +//------------------------------------------------------------------------------ +/** + * Callback used to disable/enable rendering to the texture if it is not + * visible + */ +class CameraCullCallback: + public osg::NodeCallback +{ + public: + + CameraCullCallback(): + _render( true ) + {} + + /** + * Enable rendering for the next frame + */ + void enableRendering() + { + _render = true; + } + + private: + + bool _render; + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if( _render ) + { + traverse(node, nv); + _render = false; + } + } +}; + +/** + * This callback is installed on every placement of the canvas in the scene to + * only render the canvas if at least one placement is visible + */ +class PlacementCullCallback: + public osg::NodeCallback +{ + public: + + PlacementCullCallback(Canvas* canvas, CameraCullCallback* camera_cull): + _canvas( canvas ), + _camera_cull( camera_cull ) + {} + + private: + + Canvas *_canvas; + CameraCullCallback *_camera_cull; + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + _camera_cull->enableRendering(); + traverse(node, nv); + } +}; + +//------------------------------------------------------------------------------ +Canvas::Canvas(): + _size_x(-1), + _size_y(-1), + _view_width(-1), + _view_height(-1), + _status(0), + _node(0), + _sampling_dirty(false) +{ + setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y); + + CameraCullCallback *camera_callback = new CameraCullCallback; + _camera_callback = camera_callback; + _cull_callback = new PlacementCullCallback(this, camera_callback); +} + +//------------------------------------------------------------------------------ +Canvas::~Canvas() +{ + +} + +//------------------------------------------------------------------------------ +int Canvas::getStatus() const +{ + return _status; +} + +//------------------------------------------------------------------------------ +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; + } + + setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y); + + if( node ) + { + _node = node; + _node->tie + ( + "size[0]", + SGRawValueMethods( *this, &Canvas::getSizeX, + &Canvas::setSizeX ) + ); + _node->tie + ( + "size[1]", + SGRawValueMethods( *this, &Canvas::getSizeY, + &Canvas::setSizeY ) + ); + _node->tie + ( + "view[0]", + SGRawValueMethods( *this, &Canvas::getViewWidth, + &Canvas::setViewWidth ) + ); + _node->tie + ( + "view[1]", + SGRawValueMethods( *this, &Canvas::getViewHeight, + &Canvas::setViewHeight ) + ); + _node->tie + ( + "status", + SGRawValueMethods(*this, &Canvas::getStatus) + ); + _node->tie + ( + "status-msg", + SGRawValueMethods(*this, &Canvas::getStatusMsg) + ); + _node->addChangeListener(this); + } +} + +//------------------------------------------------------------------------------ +void Canvas::update(double delta_time_sec) +{ + if( !_texture.serviceable() ) + { + if( _status != STATUS_OK ) + return; + + _texture.setSize(_size_x, _size_y); + _texture.useImageCoords(true); + _texture.useStencil(true); + _texture.allocRT(_camera_callback); + _texture.getCamera()->setClearColor(osg::Vec4(0.0f, 0.0f, 0.0f , 1.0f)); + + for( size_t i = 0; i < _groups.size(); ++i ) + _texture.getCamera()->addChild( _groups[i]->getMatrixTransform() ); + + if( _texture.serviceable() ) + { + setStatusFlags(STATUS_OK); + } + else + { + setStatusFlags(CREATE_FAILED); + return; + } + } + + for( size_t i = 0; i < _groups.size(); ++i ) + _groups[i]->update(delta_time_sec); + + if( _sampling_dirty ) + { + _texture.setSampling( + _node->getBoolValue("mipmapping"), + _node->getIntValue("coverage-samples"), + _node->getIntValue("color-samples") + ); + _sampling_dirty = false; + } + + while( !_dirty_placements.empty() ) + { + SGPropertyNode *node = _dirty_placements.back(); + _dirty_placements.pop_back(); + + if( node->getIndex() >= static_cast(_placements.size()) ) + // New placement + _placements.resize(node->getIndex() + 1); + else + // Remove maybe already existing placements + clearPlacements(node->getIndex()); + + // add new placements + _placements[node->getIndex()] = _texture.set_texture( + node, + _texture.getTexture(), + _cull_callback + ); + } +} + +//------------------------------------------------------------------------------ +void Canvas::setSizeX(int sx) +{ + if( _size_x == sx ) + return; + _size_x = sx; + + // TODO resize if texture already allocated + + if( _size_x <= 0 ) + setStatusFlags(MISSING_SIZE_X); + else + setStatusFlags(MISSING_SIZE_X, false); + + // reset flag to allow creation with new size + setStatusFlags(CREATE_FAILED, false); +} + +//------------------------------------------------------------------------------ +int Canvas::getSizeX() const +{ + return _size_x; +} + +//------------------------------------------------------------------------------ +void Canvas::setSizeY(int sy) +{ + if( _size_y == sy ) + return; + _size_y = sy; + + // TODO resize if texture already allocated + + if( _size_y <= 0 ) + setStatusFlags(MISSING_SIZE_Y); + else + setStatusFlags(MISSING_SIZE_Y, false); + + // reset flag to allow creation with new size + setStatusFlags(CREATE_FAILED, false); +} + +//------------------------------------------------------------------------------ +int Canvas::getSizeY() const +{ + return _size_y; +} + +//------------------------------------------------------------------------------ +void Canvas::setViewWidth(int w) +{ + if( _view_width == w ) + return; + _view_width = w; + + _texture.setViewSize(_view_width, _view_height); +} + +//------------------------------------------------------------------------------ +int Canvas::getViewWidth() const +{ + return _view_width; +} + +//------------------------------------------------------------------------------ +void Canvas::setViewHeight(int h) +{ + if( _view_height == h ) + return; + _view_height = h; + + _texture.setViewSize(_view_width, _view_height); +} + +//------------------------------------------------------------------------------ +int Canvas::getViewHeight() const +{ + return _view_height; +} + +//------------------------------------------------------------------------------ +const char* Canvas::getStatusMsg() const +{ + return _status_msg.c_str(); +} + +//------------------------------------------------------------------------------ +void Canvas::childAdded( SGPropertyNode * parent, + SGPropertyNode * child ) +{ + if( parent != _node ) + return; + + if( child->getNameString() == "group" ) + { + _groups.push_back + ( + boost::shared_ptr(new canvas::Group(child)) + ); + if( _texture.serviceable() ) + _texture.getCamera()->addChild( _groups.back()->getMatrixTransform() ); + } + else if( child->getNameString() == "placement" ) + { + _dirty_placements.push_back(child); + } +// else +// std::cout << "Canvas::childAdded: " << child->getPath() << std::endl; +} + +//------------------------------------------------------------------------------ +void Canvas::childRemoved( SGPropertyNode * parent, + SGPropertyNode * child ) +{ + if( parent != _node ) + return; + + 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" ) + { + // 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()); + } + else if( node->getParent() == _node ) + { + if( node->getNameString() == "mipmapping" + || node->getNameString() == "coverage-samples" + || node->getNameString() == "color-samples" ) + _sampling_dirty = true; + } +} + +//------------------------------------------------------------------------------ +void Canvas::setStatusFlags(unsigned int flags, bool set) +{ + if( set ) + _status |= flags; + else + _status &= ~flags; + + if( (_status & MISSING_SIZE_X) && (_status & MISSING_SIZE_Y) ) + _status_msg = "Missing size"; + else if( _status & MISSING_SIZE_X ) + _status_msg = "Missing size-x"; + else if( _status & MISSING_SIZE_Y ) + _status_msg = "Missing size-y"; + else if( _status & CREATE_FAILED ) + _status_msg = "Creating render target failed"; + else if( _status == STATUS_OK && !_texture.serviceable() ) + _status_msg = "Creation pending..."; + else + _status_msg = "Ok"; +} + +//------------------------------------------------------------------------------ +void Canvas::clearPlacements(int index) +{ + Placements& placements = _placements.at(index); + while( !placements.empty() ) + { + osg::ref_ptr group = placements.back(); + placements.pop_back(); + + assert( group->getNumParents() == 1 ); + assert( group->getNumChildren() == 1 ); + + osg::Group *parent = group->getParent(0); + osg::Node *child = group->getChild(0); + + parent->addChild(child); + group->removeChild(child); + parent->removeChild(group); + } +} diff --git a/src/Canvas/canvas.hxx b/src/Canvas/canvas.hxx new file mode 100644 index 000000000..1769b8ed4 --- /dev/null +++ b/src/Canvas/canvas.hxx @@ -0,0 +1,101 @@ +// The canvas for rendering with the 2d api +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 CANVAS_HXX_ +#define CANVAS_HXX_ + +#include +#include +#include + +#include +#include + +namespace canvas +{ + class Group; +} + +class Canvas: + public SGPropertyChangeListener +{ + public: + + enum StatusFlags + { + STATUS_OK, + MISSING_SIZE_X = 0x0001, + MISSING_SIZE_Y = 0x0002, + CREATE_FAILED = 0x0004 + }; + + Canvas(); + virtual ~Canvas(); + + void reset(SGPropertyNode* node); + void update(double delta_time_sec); + + void setSizeX(int sx); + int getSizeX() const; + + void setSizeY(int sy); + int getSizeY() const; + + void setViewWidth(int w); + int getViewWidth() const; + + void setViewHeight(int h); + int getViewHeight() const; + + int getStatus() const; + const char* getStatusMsg() const; + + virtual void childAdded( SGPropertyNode * parent, + SGPropertyNode * child ); + virtual void childRemoved( SGPropertyNode * parent, + SGPropertyNode * child ); + virtual void valueChanged (SGPropertyNode * node); + + private: + + int _size_x, + _size_y, + _view_width, + _view_height; + + int _status; + std::string _status_msg; + + FGODGauge _texture; + SGPropertyNode *_node; + + bool _sampling_dirty; + + osg::ref_ptr _camera_callback; + osg::ref_ptr _cull_callback; + std::vector _dirty_placements; + std::vector _placements; + + // TODO replace auto_ptr with unique_ptr as soon as C++11 is used! + std::vector > _groups; + + void setStatusFlags(unsigned int flags, bool set = true); + void clearPlacements(int index); +}; + +#endif /* CANVAS_HXX_ */ diff --git a/src/Canvas/canvas_mgr.cxx b/src/Canvas/canvas_mgr.cxx new file mode 100644 index 000000000..eab30b581 --- /dev/null +++ b/src/Canvas/canvas_mgr.cxx @@ -0,0 +1,144 @@ +// Canvas with 2D rendering api +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 "canvas_mgr.hxx" +#include "canvas.hxx" + +#include
+ +#include +#include + +#include +#include + +//------------------------------------------------------------------------------ +CanvasMgr::CanvasMgr(): + _props( fgGetNode("/canvas", true) ) +{ + +} + +//------------------------------------------------------------------------------ +CanvasMgr::~CanvasMgr() +{ + +} + +//------------------------------------------------------------------------------ +void CanvasMgr::init() +{ + _props->addChangeListener(this); + triggerChangeRecursive(_props); +} + +//------------------------------------------------------------------------------ +void CanvasMgr::reinit() +{ + +} + +//------------------------------------------------------------------------------ +void CanvasMgr::shutdown() +{ + _props->removeChangeListener(this); +} + +//------------------------------------------------------------------------------ +void CanvasMgr::bind() +{ +} + +//------------------------------------------------------------------------------ +void CanvasMgr::unbind() +{ +} + +//------------------------------------------------------------------------------ +void CanvasMgr::update(double delta_time_sec) +{ + for( size_t i = 0; i < _canvases.size(); ++i ) + _canvases[i].update(delta_time_sec); +} + +//------------------------------------------------------------------------------ +void CanvasMgr::childAdded( SGPropertyNode * parent, + SGPropertyNode * child ) +{ + if( parent != _props ) + return; + + if( child->getNameString() == "texture" ) + textureAdded(child); + else + std::cout << "CanvasMgr::childAdded: " << child->getPath() << std::endl; +} + +//------------------------------------------------------------------------------ +void CanvasMgr::childRemoved( SGPropertyNode * parent, + SGPropertyNode * child ) +{ + if( parent != _props ) + return; + + std::cout << "CanvasMgr::childRemoved: " << child->getPath() << std::endl; +} + +//------------------------------------------------------------------------------ +void CanvasMgr::textureAdded(SGPropertyNode* node) +{ + size_t index = node->getIndex(); + + if( index >= _canvases.size() ) + { + 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); +} + +//------------------------------------------------------------------------------ +void CanvasMgr::triggerChangeRecursive(SGPropertyNode* node) +{ + node->getParent()->fireChildAdded(node); + + if( node->nChildren() == 0 && node->getType() != simgear::props::NONE ) + return node->fireValueChanged(); + + for( int i = 0; i < node->nChildren(); ++i ) + triggerChangeRecursive( node->getChild(i) ); +} + +//------------------------------------------------------------------------------ +template +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(child); +} diff --git a/src/Canvas/canvas_mgr.hxx b/src/Canvas/canvas_mgr.hxx new file mode 100644 index 000000000..b3b5bac0e --- /dev/null +++ b/src/Canvas/canvas_mgr.hxx @@ -0,0 +1,76 @@ +// Canvas with 2D rendering api +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 CANVAS_MGR_H_ +#define CANVAS_MGR_H_ + +#include +#include + +#include +#include + +class Canvas; + +class CanvasMgr: + public SGSubsystem, + public SGPropertyChangeListener +{ + public: + CanvasMgr(); + virtual ~CanvasMgr(); + + virtual void init(); + virtual void reinit(); + virtual void shutdown(); + + virtual void bind(); + virtual void unbind(); + + virtual void update(double delta_time_sec); + + virtual void childAdded( SGPropertyNode * parent, + SGPropertyNode * child ); + virtual void childRemoved( SGPropertyNode * parent, + SGPropertyNode * child ); + + private: + + /** Root node for everything concerning the canvas system */ + SGPropertyNode_ptr _props; + + /** The actual canvases */ + std::vector _canvases; + + void textureAdded(SGPropertyNode* node); + + /** + * Trigger a childAdded and valueChanged event for every child of node + * (Unlimited depth) and node itself. + */ + void triggerChangeRecursive(SGPropertyNode* node); + + /** + * Get the value of a property or throw an exception if it doesn't exist. + */ + template + T getParam(const SGPropertyNode* node, const char* prop); + +}; + +#endif /* CANVAS_MGR_H_ */ diff --git a/src/Canvas/elements/element.cxx b/src/Canvas/elements/element.cxx new file mode 100644 index 000000000..119f109e6 --- /dev/null +++ b/src/Canvas/elements/element.cxx @@ -0,0 +1,266 @@ +// Interface for 2D canvas element +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 "element.hxx" +#include + +#include + +namespace canvas +{ + const std::string NAME_TRANSFORM = "tf"; + const std::string NAME_COLOR = "color"; + const std::string NAME_COLOR_FILL = "color-fill"; + + //---------------------------------------------------------------------------- + Element::~Element() + { + + } + + //---------------------------------------------------------------------------- + SGPropertyNode* Element::getPropertyNode() + { + return _node; + } + + //---------------------------------------------------------------------------- + void Element::update(double dt) + { + if( _transform_dirty ) + { + osg::Matrix m; + for( size_t i = 0; i < _transform_types.size(); ++i ) + { + // Skip unused indizes... + if( _transform_types[i] == TT_NONE ) + continue; + + SGPropertyNode* tf_node = _node->getChild("tf", i, true); + + // Build up the matrix representation of the current transform node + osg::Matrix tf; + switch( _transform_types[i] ) + { + case TT_MATRIX: + tf = osg::Matrix( tf_node->getDoubleValue("a", 1), + tf_node->getDoubleValue("b", 0), 0, 0, + + tf_node->getDoubleValue("c", 0), + tf_node->getDoubleValue("d", 1), 0, 0, + + 0, 0, 1, 0, + tf_node->getDoubleValue("e", 0), + tf_node->getDoubleValue("f", 0), 0, 1 ); + break; + case TT_TRANSLATE: + tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("tx", 0), + tf_node->getDoubleValue("ty", 0), + 0 ) ); + break; + case TT_ROTATE: + tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 ); + break; + case TT_SCALE: + { + float sx = tf_node->getDoubleValue("sx", 1); + // sy defaults to sx... + tf.makeScale( sx, tf_node->getDoubleValue("sy", sx), 1 ); + break; + } + default: + break; + } + m.postMult( tf ); + } + _transform->setMatrix(m); + _transform_dirty = false; + } + + if( _attributes_dirty & COLOR ) + { + colorChanged( osg::Vec4( _color[0]->getFloatValue(), + _color[1]->getFloatValue(), + _color[2]->getFloatValue(), + 1 ) ); + _attributes_dirty &= ~COLOR; + } + + if( _attributes_dirty & COLOR_FILL ) + { + colorFillChanged( osg::Vec4( _color_fill[0]->getFloatValue(), + _color_fill[1]->getFloatValue(), + _color_fill[2]->getFloatValue(), + 1 ) ); + _attributes_dirty &= ~COLOR_FILL; + } + + if( _drawable && (_attributes_dirty & BOUNDING_BOX) ) + { + _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()); + + _attributes_dirty &= ~BOUNDING_BOX; + } + } + + //---------------------------------------------------------------------------- + osg::ref_ptr Element::getMatrixTransform() + { + return _transform; + } + + //---------------------------------------------------------------------------- + Element::Element(SGPropertyNode* node, uint32_t attributes_used): + _node( node ), + _drawable( 0 ), + _attributes_used( attributes_used ), + _attributes_dirty( 0 ), + _transform_dirty( false ), + _transform( new osg::MatrixTransform ) + { + assert( _node ); + _node->addChangeListener(this); + + if( _attributes_used & COLOR ) + linkColorNodes("color", _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); + } + + SG_LOG + ( + SG_GL, + SG_DEBUG, + "New canvas element " << node->getPath() + ); + } + + //---------------------------------------------------------------------------- + void Element::linkColorNodes( const char* name, + SGPropertyNode** nodes, + const osg::Vec4& def ) + { + // Don't tie to allow the usage of aliases + SGPropertyNode* color = _node->getChild(name, 0, true); + + static const char* color_names[] = {"red", "green", "blue"}; + for( size_t i = 0; i < sizeof(color_names)/sizeof(color_names[0]); ++i ) + { + color->setFloatValue + ( + color_names[i], + color->getFloatValue(color_names[i], def[i]) + ); + nodes[i] = color->getChild(color_names[i]); + } + } + + //---------------------------------------------------------------------------- + void Element::childAdded(SGPropertyNode* parent, SGPropertyNode* child) + { + if( parent == _node ) + { + if( child->getNameString() == NAME_TRANSFORM ) + { + if( child->getIndex() >= static_cast(_transform_types.size()) ) + _transform_types.resize( child->getIndex() + 1 ); + + _transform_types[ child->getIndex() ] = TT_NONE; + _transform_dirty = true; + } + else + childAdded(child); + } + else if( parent->getParent() == _node + && parent->getNameString() == NAME_TRANSFORM ) + { + assert(parent->getIndex() < static_cast(_transform_types.size())); + + const std::string& name = child->getNameString(); + + TransformType& type = _transform_types[parent->getIndex()]; + + if( name == "a" || name == "b" || name == "c" + || name == "d" || name == "e" || name == "f" ) + type = TT_MATRIX; + else if( name == "tx" || name == "ty" ) + type = TT_TRANSLATE; + else if( name == "rot" ) + type = TT_ROTATE; + else if( name == "sx" || name == "sy" ) + type = TT_SCALE; + else + SG_LOG + ( + SG_GL, + SG_WARN, + "Unknown transform element " << child->getPath() + ); + + _transform_dirty = true; + } + } + + //---------------------------------------------------------------------------- + void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child) + { + if( parent != _node ) + return; + + if( child->getNameString() == NAME_TRANSFORM ) + { + assert(child->getIndex() < static_cast(_transform_types.size())); + _transform_types[ child->getIndex() ] = TT_NONE; + + while( !_transform_types.empty() && _transform_types.back() == TT_NONE ) + _transform_types.pop_back(); + + _transform_dirty = true; + } + else + childRemoved(child); + } + + //---------------------------------------------------------------------------- + void Element::valueChanged(SGPropertyNode* child) + { + SGPropertyNode *parent = child->getParent(); + if( parent->getParent() == _node ) + { + if( parent->getNameString() == NAME_TRANSFORM ) + _transform_dirty = true; + else if( parent->getNameString() == NAME_COLOR ) + _attributes_dirty |= COLOR; + else if( parent->getNameString() == NAME_COLOR_FILL ) + _attributes_dirty |= COLOR_FILL; + } + else + childChanged(child); + } + +} // namespace canvas diff --git a/src/Canvas/elements/element.hxx b/src/Canvas/elements/element.hxx new file mode 100644 index 000000000..2588f9cc4 --- /dev/null +++ b/src/Canvas/elements/element.hxx @@ -0,0 +1,106 @@ +// Interface for 2D canvas element +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 CANVAS_ELEMENT_HXX_ +#define CANVAS_ELEMENT_HXX_ + +#include +#include + +namespace osg +{ + class Drawable; +} + +namespace canvas +{ + + class Element: + private SGPropertyChangeListener + { + public: + virtual ~Element() = 0; + SGPropertyNode* getPropertyNode(); + + /** + * Called every frame to update internal state + * + * @param dt Frame time in seconds + */ + virtual void update(double dt); + + osg::ref_ptr getMatrixTransform(); + + protected: + + enum Attributes + { + COLOR = 0x0001, + COLOR_FILL = 0x0002, + BOUNDING_BOX = 0x0004 + }; + + enum TransformType + { + TT_NONE, + TT_MATRIX, + TT_TRANSLATE, + TT_ROTATE, + TT_SCALE + }; + + SGPropertyNode *_node; + osg::Drawable *_drawable; + + uint32_t _attributes_used; + uint32_t _attributes_dirty; + + bool _transform_dirty; + osg::ref_ptr _transform; + std::vector _transform_types; + + SGPropertyNode *_bounding_box[4]; /// +// +// 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 "group.hxx" +#include "text.hxx" + +namespace canvas +{ + + //---------------------------------------------------------------------------- + Group::Group(SGPropertyNode* node): + Element(node) + { + + } + + //---------------------------------------------------------------------------- + Group::~Group() + { + + } + + //---------------------------------------------------------------------------- + void Group::update(double dt) + { + for( size_t i = 0; i < _children.size(); ++i ) + _children[i]->update(dt); + + Element::update(dt); + } + + //---------------------------------------------------------------------------- + void Group::childAdded(SGPropertyNode* child) + { + if( child->getNameString() == "text" ) + { + _children.push_back( boost::shared_ptr(new Text(child)) ); + _transform->addChild( _children.back()->getMatrixTransform() ); + } + else + std::cout << "New unknown child: " << child->getDisplayName() << std::endl; + } + + //---------------------------------------------------------------------------- + void Group::childRemoved(SGPropertyNode* child) + { + + } + +} // namespace canvas diff --git a/src/Canvas/elements/group.hxx b/src/Canvas/elements/group.hxx new file mode 100644 index 000000000..ccfea07be --- /dev/null +++ b/src/Canvas/elements/group.hxx @@ -0,0 +1,47 @@ +// A group of 2D canvas elements +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 CANVAS_GROUP_HXX_ +#define CANVAS_GROUP_HXX_ + +#include "element.hxx" +#include +#include + +namespace canvas +{ + + class Group: + public Element + { + public: + Group(SGPropertyNode* node); + virtual ~Group(); + + virtual void update(double dt); + + protected: + std::vector > _children; + + virtual void childAdded(SGPropertyNode * child); + virtual void childRemoved(SGPropertyNode * child); + }; + +} // namespace canvas + +#endif /* CANVAS_GROUP_HXX_ */ diff --git a/src/Canvas/elements/text-alignment.hxx b/src/Canvas/elements/text-alignment.hxx new file mode 100644 index 000000000..0c82e7d7b --- /dev/null +++ b/src/Canvas/elements/text-alignment.hxx @@ -0,0 +1,41 @@ +// Mapping between strings and osg text alignment flags +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 ENUM_MAPPING +# error "Only include with ENUM_MAPPING defined!" +#endif + +ENUM_MAPPING(LEFT_TOP, "left-top") +ENUM_MAPPING(LEFT_CENTER, "left-center") +ENUM_MAPPING(LEFT_BOTTOM, "left-bottom") + +ENUM_MAPPING(CENTER_TOP, "center-top") +ENUM_MAPPING(CENTER_CENTER, "center-center") +ENUM_MAPPING(CENTER_BOTTOM, "center-bottom") + +ENUM_MAPPING(RIGHT_TOP, "right-top") +ENUM_MAPPING(RIGHT_CENTER, "right-center") +ENUM_MAPPING(RIGHT_BOTTOM, "right-bottom") + +ENUM_MAPPING(LEFT_BASE_LINE, "left-baseline") +ENUM_MAPPING(CENTER_BASE_LINE, "center-baseline") +ENUM_MAPPING(RIGHT_BASE_LINE, "right-baseline") + +ENUM_MAPPING(LEFT_BOTTOM_BASE_LINE, "left-bottom-baseline") +ENUM_MAPPING(CENTER_BOTTOM_BASE_LINE, "center-bottom-baseline") +ENUM_MAPPING(RIGHT_BOTTOM_BASE_LINE, "right-bottom-baseline") diff --git a/src/Canvas/elements/text.cxx b/src/Canvas/elements/text.cxx new file mode 100644 index 000000000..af27cda07 --- /dev/null +++ b/src/Canvas/elements/text.cxx @@ -0,0 +1,184 @@ +// A text on the canvas +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 "text.hxx" +#include + +#include
+#include
+ +namespace canvas +{ + + //---------------------------------------------------------------------------- + Text::Text(SGPropertyNode* node): + Element(node, COLOR | COLOR_FILL | BOUNDING_BOX), + _text( new osgText::Text ) + { + _drawable = _text; + _text->setCharacterSizeMode(osgText::Text::SCREEN_COORDS); + + // font size and property node + float character_size = _node->getFloatValue("size", 32); + _text->setCharacterSize( character_size ); + _node->setFloatValue("size", character_size); + + osg::ref_ptr geode = new osg::Geode; + geode->addDrawable(_text); + _transform->addChild(geode); + + _node->tie + ( + "alignment", + SGRawValueMethods + ( + *this, + &Text::getAlignment, + &Text::setAlignment + ) + ); + _node->tie + ( + "padding", + SGRawValueMethods + ( + *_text.get(), + &osgText::Text::getBoundingBoxMargin, + &osgText::Text::setBoundingBoxMargin + ) + ); + typedef SGRawValueMethods TextIntMethods; + _node->tie + ( + "draw-mode", + // TEXT = 1 default + // BOUNDINGBOX = 2 + // FILLEDBOUNDINGBOX = 4 + // ALIGNMENT = 8 + TextIntMethods + ( + *_text.get(), + (TextIntMethods::getter_t)&osgText::Text::getDrawMode, + (TextIntMethods::setter_t)&osgText::Text::setDrawMode + ) + ); + } + + //---------------------------------------------------------------------------- + Text::~Text() + { + + } + + //---------------------------------------------------------------------------- + void Text::setFont(const char* name) + { + _text->setFont( getFont(name) ); + } + + //---------------------------------------------------------------------------- + void Text::setAlignment(const char* align) + { + const std::string align_string(align); + if( 0 ) return; +#define ENUM_MAPPING(enum_val, string_val) \ + else if( align_string == string_val )\ + _text->setAlignment( osgText::Text::enum_val ); +#include "text-alignment.hxx" +#undef ENUM_MAPPING + else + _text->setAlignment(osgText::Text::LEFT_BASE_LINE); + } + + //---------------------------------------------------------------------------- + const char* Text::getAlignment() const + { + switch( _text->getAlignment() ) + { +#define ENUM_MAPPING(enum_val, string_val) \ + case osgText::Text::enum_val:\ + return string_val; +#include "text-alignment.hxx" +#undef ENUM_MAPPING + default: + return "unknown"; + } + } + + //---------------------------------------------------------------------------- + void Text::childChanged(SGPropertyNode* child) + { + if( child->getNameString() == "text" ) + _text->setText(child->getStringValue()); + else if( child->getNameString() == "size" ) + _text->setCharacterSize( child->getFloatValue() ); + else if( child->getNameString() == "font" ) + setFont( child->getStringValue() ); + else + return; + + _attributes_dirty |= BOUNDING_BOX; + } + + //---------------------------------------------------------------------------- + void Text::colorChanged(const osg::Vec4& color) + { + _text->setColor(color); + } + + //---------------------------------------------------------------------------- + void Text::colorFillChanged(const osg::Vec4& color) + { + _text->setBoundingBoxColor(color); + } + + //---------------------------------------------------------------------------- + Text::font_ptr Text::getFont(const std::string& name) + { + SGPath path = globals->resolve_ressource_path("Fonts/" + name); + if( path.isNull() ) + { + SG_LOG + ( + SG_GL, + SG_ALERT, + "canvas::Text: No such font: " << name + ); + return font_ptr(); + } + + SG_LOG + ( + SG_GL, + SG_INFO, + "canvas::Text: using font file " << path.str() + ); + + font_ptr font = osgText::readFontFile(path.c_str()); + if( !font ) + SG_LOG + ( + SG_GL, + SG_ALERT, + "canvas::Text: Failed to open font file " << path.c_str() + ); + + return font; + } + +} // namespace canvas diff --git a/src/Canvas/elements/text.hxx b/src/Canvas/elements/text.hxx new file mode 100644 index 000000000..38d508b00 --- /dev/null +++ b/src/Canvas/elements/text.hxx @@ -0,0 +1,56 @@ +// A text on the canvas +// +// Copyright (C) 2012 Thomas Geymayer +// +// 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 CANVAS_TEXT_HXX_ +#define CANVAS_TEXT_HXX_ + +#include "element.hxx" +#include +#include +#include +#include + +namespace canvas +{ + + class Text: + public Element + { + public: + Text(SGPropertyNode* node); + virtual ~Text(); + + void setFont(const char* name); + + void setAlignment(const char* align); + const char* getAlignment() const; + + protected: + osg::ref_ptr _text; + + virtual void childChanged(SGPropertyNode * child); + virtual void colorChanged(const osg::Vec4& color); + virtual void colorFillChanged(const osg::Vec4& color); + + typedef osg::ref_ptr font_ptr; + static font_ptr getFont(const std::string& name); + }; + +} // namespace canvas + +#endif /* CANVAS_TEXT_HXX_ */ diff --git a/src/Instrumentation/od_gauge.cxx b/src/Instrumentation/od_gauge.cxx index 6760bcd22..1b7659365 100644 --- a/src/Instrumentation/od_gauge.cxx +++ b/src/Instrumentation/od_gauge.cxx @@ -6,6 +6,10 @@ // // Ported to OSG by Tim Moore - Jun 2007 // +// Heavily modified to be usable for the 2d Canvas by Thomas Geymayer - April 2012 +// Supports now multisampling/mipmapping, usage of the stencil buffer and placing +// the texture in the scene by certain filter criteria +// // 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 @@ -38,133 +42,399 @@ #include #include +#include #include -#include #include
#include #include #include "od_gauge.hxx" -FGODGauge::FGODGauge() : +#include + +//------------------------------------------------------------------------------ +FGODGauge::FGODGauge(): + _size_x( -1 ), + _size_y( -1 ), + _view_width( -1 ), + _view_height( -1 ), + _use_image_coords( false ), + _use_stencil( false ), + _use_mipmapping( false ), + _coverage_samples( 0 ), + _color_samples( 0 ), rtAvailable( false ) { } -void FGODGauge::allocRT () -{ - camera = new osg::Camera; - // Only the far camera should trigger this texture to be rendered. - camera->setNodeMask(simgear::BACKGROUND_BIT); - camera->setProjectionMatrix(osg::Matrix::ortho2D(-textureWH/2.0, textureWH/2.0, -textureWH/2.0, textureWH/2.0)); - camera->setViewport(0, 0, textureWH, textureWH); - camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); - camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - camera->setClearColor(osg::Vec4(0.0f, 0.0f, 0.0f , 0.0f)); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::FRAME_BUFFER); - osg::StateSet* stateSet = camera->getOrCreateStateSet(); - stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - stateSet->setMode(GL_FOG, osg::StateAttribute::OFF); - stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, - osg::PolygonMode::FILL), - osg::StateAttribute::ON); - stateSet->setAttributeAndModes(new osg::AlphaFunc(osg::AlphaFunc::GREATER, - 0.0f), - osg::StateAttribute::ON); - stateSet->setAttribute(new osg::ShadeModel(osg::ShadeModel::FLAT)); - stateSet->setAttributeAndModes(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, - osg::BlendFunc::ONE_MINUS_SRC_ALPHA), - osg::StateAttribute::ON); - if (!texture.valid()) { - texture = new osg::Texture2D; - texture->setTextureSize(textureWH, textureWH); - texture->setInternalFormat(GL_RGBA); - texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR); - texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR); - } - camera->attach(osg::Camera::COLOR_BUFFER, texture.get()); - globals->get_renderer()->addCamera(camera.get(), false); - rtAvailable = true; -} - +//------------------------------------------------------------------------------ FGODGauge::~FGODGauge() { - if (camera.valid()) { - globals->get_renderer()->removeCamera(camera.get()); - } + if( camera.valid() ) + globals->get_renderer()->removeCamera(camera.get()); } -void FGODGauge::setSize(int viewSize) +//------------------------------------------------------------------------------ +void FGODGauge::setSize(int size_x, int size_y) { - textureWH = viewSize; - if (texture.valid()) { - texture->setTextureSize(textureWH, textureWH); - } + _size_x = size_x; + _size_y = size_y < 0 ? size_x : size_y; + + if( texture.valid() ) + texture->setTextureSize(_size_x, _size_x); } +//---------------------------------------------------------------------------- +void FGODGauge::setViewSize(int width, int height) +{ + _view_width = width; + _view_height = height < 0 ? width : height; + + if( camera ) + updateCoordinateFrame(); +} + +//------------------------------------------------------------------------------ +void FGODGauge::useImageCoords(bool use) +{ + if( use == _use_image_coords ) + return; + + _use_image_coords = use; + + if( texture ) + updateCoordinateFrame(); +} + +//------------------------------------------------------------------------------ +void FGODGauge::useStencil(bool use) +{ + if( use == _use_stencil ) + return; + + _use_stencil = use; + + if( texture ) + updateStencil(); +} + +//------------------------------------------------------------------------------ +void FGODGauge::setSampling( bool mipmapping, + int coverage_samples, + int color_samples ) +{ + if( _use_mipmapping == mipmapping + && _coverage_samples == coverage_samples + && _color_samples == color_samples ) + return; + + _use_mipmapping = mipmapping; + + if( color_samples > coverage_samples ) + { + SG_LOG + ( + SG_GL, + SG_WARN, + "FGODGauge::setSampling: color_samples > coverage_samples not allowed!" + ); + color_samples = coverage_samples; + } + + _coverage_samples = coverage_samples; + _color_samples = color_samples; + + updateSampling(); +} + +//------------------------------------------------------------------------------ bool FGODGauge::serviceable(void) { - return rtAvailable; + return rtAvailable; +} + +//------------------------------------------------------------------------------ +void FGODGauge::allocRT(osg::NodeCallback* camera_cull_callback) +{ + camera = new osg::Camera; + camera->setDataVariance(osg::Object::DYNAMIC); + // Only the far camera should trigger this texture to be rendered. + camera->setNodeMask(simgear::BACKGROUND_BIT); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setClearColor(osg::Vec4(0.0f, 0.0f, 0.0f , 0.0f)); + camera->setClearStencil(0); + camera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT, + osg::Camera::FRAME_BUFFER ); + + if( camera_cull_callback ) + camera->setCullCallback(camera_cull_callback); + + updateCoordinateFrame(); + updateStencil(); + + osg::StateSet* stateSet = camera->getOrCreateStateSet(); + stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateSet->setMode(GL_FOG, osg::StateAttribute::OFF); + stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, + osg::PolygonMode::FILL), + osg::StateAttribute::ON); + stateSet->setAttributeAndModes(new osg::AlphaFunc(osg::AlphaFunc::GREATER, + 0.0f), + osg::StateAttribute::ON); + stateSet->setAttribute(new osg::ShadeModel(osg::ShadeModel::FLAT)); + stateSet->setAttributeAndModes(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, + osg::BlendFunc::ONE_MINUS_SRC_ALPHA), + osg::StateAttribute::ON); + if( !texture ) + { + texture = new osg::Texture2D; + texture->setTextureSize(_size_x, _size_y); + texture->setInternalFormat(GL_RGBA); + } + + updateSampling(); + + globals->get_renderer()->addCamera(camera.get(), false); + rtAvailable = true; +} + +//------------------------------------------------------------------------------ +void FGODGauge::updateCoordinateFrame() +{ + assert( camera ); + + if( _view_width < 0 ) + _view_width = _size_x; + if( _view_height < 0 ) + _view_height = _size_y; + + camera->setViewport(0, 0, _size_x, _size_y); + + if( _use_image_coords ) + camera->setProjectionMatrix( + osg::Matrix::ortho2D(0, _view_width, _view_height, 0) + ); + else + camera->setProjectionMatrix( + osg::Matrix::ortho2D( -_view_width/2., _view_width/2., + -_view_height/2., _view_height/2. ) + ); +} + +//------------------------------------------------------------------------------ +void FGODGauge::updateStencil() +{ + assert( camera ); + + GLbitfield mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; + + if( _use_stencil) + { + camera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, + GL_DEPTH_STENCIL_EXT ); + mask |= GL_STENCIL_BUFFER_BIT; + } + else + { + camera->detach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER); + } + + camera->setClearMask(mask); +} + +//------------------------------------------------------------------------------ +void FGODGauge::updateSampling() +{ + assert( camera ); + assert( texture ); + + texture->setFilter( + osg::Texture2D::MIN_FILTER, + _use_mipmapping ? osg::Texture2D::LINEAR_MIPMAP_NEAREST + : osg::Texture2D::LINEAR + ); + camera->attach( + osg::Camera::COLOR_BUFFER, + texture.get(), + 0, 0, + _use_mipmapping, + _coverage_samples, + _color_samples + ); } /** * Replace a texture in the airplane model with the gauge texture. */ - -class ReplaceStaticTextureVisitor : public osg::NodeVisitor +class ReplaceStaticTextureVisitor: + public osg::NodeVisitor { -public: - ReplaceStaticTextureVisitor(const std::string& name, - osg::Texture2D* _newTexture) : + public: + + ReplaceStaticTextureVisitor( const char* name, + osg::Texture2D* new_texture ): osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), - newTexture(_newTexture) + _tex_name( osgDB::getSimpleFileName(name) ), + _new_texture(new_texture) + {} + + ReplaceStaticTextureVisitor( const SGPropertyNode* placement, + osg::Texture2D* new_texture, + osg::NodeCallback* cull_callback = 0 ): + osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _tex_name( osgDB::getSimpleFileName( + placement->getStringValue("texture")) + ), + _node_name( placement->getStringValue("node") ), + _parent_name( placement->getStringValue("parent") ), + _new_texture(new_texture), + _cull_callback(cull_callback) { - textureFileName = osgDB::getSimpleFileName(name); + if( _tex_name.empty() + && _node_name.empty() + && _parent_name.empty() ) + SG_LOG + ( + SG_GL, + SG_WARN, + "No filter criterion for replacing texture. " + " Every texture will be replaced!" + ); } - virtual void apply(osg::Node& node) + /** + * Get a list of groups which have been inserted into the scene graph to + * replace the given texture + */ + Placements& getPlacements() { - osg::StateSet* ss = node.getStateSet(); - if (ss) - changeStateSetTexture(ss); - traverse(node); + return _placements; } virtual void apply(osg::Geode& node) { - int numDrawables = node.getNumDrawables(); - for (int i = 0; i < numDrawables; i++) { - osg::Drawable* drawable = node.getDrawable(i); - osg::StateSet* ss = drawable->getStateSet(); - if (ss) - changeStateSetTexture(ss); - } - traverse(node); -} -protected: - void changeStateSetTexture(osg::StateSet *ss) - { - osg::Texture2D* tex - = dynamic_cast(ss->getTextureAttribute(0, - osg::StateAttribute::TEXTURE)); - if (!tex || tex == newTexture || !tex->getImage()) + simgear::EffectGeode* eg = dynamic_cast(&node); + if( !eg ) + return; + + osg::StateSet* ss = eg->getEffect()->getDefaultStateSet(); + if( !ss ) + return; + + osg::Group *parent = node.getParent(0); + if( !_node_name.empty() && parent->getName() != _node_name ) + return; + + if( !_parent_name.empty() ) + { + // Traverse nodes upwards starting at the parent node (skip current + // node) + const osg::NodePath& np = getNodePath(); + bool found = false; + for( int i = static_cast(np.size()) - 2; i >= 0; --i ) + { + const osg::Node* path_segment = np[i]; + const osg::Node* path_parent = path_segment->getParent(0); + + // A node without a name is always the parent of the root node of + // the model just containing the file name + if( path_parent && path_parent->getName().empty() ) return; - std::string fileName = tex->getImage()->getFileName(); - std::string simpleName = osgDB::getSimpleFileName(fileName); - if (osgDB::equalCaseInsensitive(textureFileName, simpleName)) - ss->setTextureAttribute(0, newTexture); + + if( path_segment->getName() == _parent_name ) + { + found = true; + break; + } + } + + if( !found ) + return; + } + + for( size_t unit = 0; unit < ss->getNumTextureAttributeLists(); ++unit ) + { + osg::Texture2D* tex = dynamic_cast + ( + ss->getTextureAttribute(unit, osg::StateAttribute::TEXTURE) + ); + + if( !tex || !tex->getImage() || tex == _new_texture ) + continue; + + if( !_tex_name.empty() ) + { + std::string tex_name = tex->getImage()->getFileName(); + std::string tex_name_simple = osgDB::getSimpleFileName(tex_name); + if( !osgDB::equalCaseInsensitive(_tex_name, tex_name_simple) ) + continue; + } + + // insert a new group between the geode an it's parent which overrides + // the texture + osg::ref_ptr group = new osg::Group; + group->setName("canvas texture group"); + group->addChild(eg); + parent->removeChild(eg); + parent->addChild(group); + + if( _cull_callback ) + group->setCullCallback(_cull_callback); + + _placements.push_back(group); + + osg::StateSet* stateSet = group->getOrCreateStateSet(); + stateSet->setTextureAttribute( unit, _new_texture, + osg::StateAttribute::OVERRIDE ); + stateSet->setTextureMode( unit, GL_TEXTURE_2D, + osg::StateAttribute::ON ); + + SG_LOG + ( + SG_GL, + SG_INFO, + "Replaced texture '" << _tex_name << "'" + << " for object '" << parent->getName() << "'" + << (!_parent_name.empty() ? " with parent '" + _parent_name + "'" + : "") + ); + return; + } } - std::string textureFileName; - osg::Texture2D* newTexture; + + + + protected: + + std::string _tex_name, ///get_scenery()->get_aircraft_branch(); - ReplaceStaticTextureVisitor visitor(name, new_texture); - root->accept(visitor); + osg::Group* root = globals->get_scenery()->get_aircraft_branch(); + ReplaceStaticTextureVisitor visitor(name, new_texture); + root->accept(visitor); + return visitor.getPlacements(); } +//------------------------------------------------------------------------------ +Placements FGODGauge::set_texture( const SGPropertyNode* placement, + osg::Texture2D* new_texture, + osg::NodeCallback* cull_callback ) +{ + osg::Group* root = globals->get_scenery()->get_aircraft_branch(); + ReplaceStaticTextureVisitor visitor(placement, new_texture, cull_callback); + root->accept(visitor); + return visitor.getPlacements(); +} diff --git a/src/Instrumentation/od_gauge.hxx b/src/Instrumentation/od_gauge.hxx index b867d22d4..902d20e6a 100644 --- a/src/Instrumentation/od_gauge.hxx +++ b/src/Instrumentation/od_gauge.hxx @@ -6,6 +6,10 @@ // // Ported to OSG by Tim Moore - Jun 2007 // +// Heavily modified to be usable for the 2d Canvas by Thomas Geymayer - April 2012 +// Supports now multisampling/mipmapping, usage of the stencil buffer and placing +// the texture in the scene by certain filter criteria +// // 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 @@ -25,14 +29,17 @@ #ifndef _OD_GAUGE_HXX #define _OD_GAUGE_HXX - -#include +#include +#include namespace osg { class Camera; class Texture2D; } +class SGPropertyNode; +typedef std::vector > Placements; + /** * Owner Drawn Gauge helper class. */ @@ -42,11 +49,53 @@ public: FGODGauge(); virtual ~FGODGauge(); - - void setSize(int viewSize); - int size() const - { return textureWH; } + /** + * Set the size of the render target. + * + * @param size_x X size + * @param size_y Y size. Defaults to size_x if not specified + */ + void setSize(int size_x, int size_y = -1); + + /** + * Set the size of the viewport + * + * @param width + * @param height Defaults to width if not specified + */ + void setViewSize(int width, int height = -1); + + /** + * DEPRECATED + * + * Get size of squared texture + */ + int size() const { return _size_x; } + /** + * Set whether to use image coordinates or not. + * + * Default: origin == center of texture + * Image Coords: origin == top left corner + */ + void useImageCoords(bool use = true); + + /** + * Enable/Disable using a stencil buffer + */ + void useStencil(bool use = true); + + /** + * Set sampling parameters for mipmapping and coverage sampling + * antialiasing. + * + * @note color_samples is not allowed to be higher than coverage_samples + * + */ + void setSampling( bool mipmapping, + int coverage_samples = 0, + int color_samples = 0 ); + /** * Say if we can render to a texture. * @return true if rtt is available @@ -58,8 +107,27 @@ public: * This is to replace a static texture by a dynamic one * @param name texture filename * @param new_texture dynamic texture to replace the old one + * @return A list of groups which override the given texture */ - void set_texture(const char * name, osg::Texture2D* new_texture); + Placements set_texture(const char * name, osg::Texture2D* new_texture); + + /** + * Replace an opengl texture name inside the aircraft scene graph. + * This is to replace a static texture by a dynamic one. The replacement + * is base on certain filtering criteria which have to be stored in string + * value childs of the placement node. Recognized nodes are: + * - texture Match the name of the texture + * - node Match the name of the object + * - parent Match any of the object parents names (all the tree upwards) + * @param placement the node containing the replacement criteria + * @param new_texture dynamic texture to replace the old one + * @param an optional cull callback which will be installed on any matching + * object + * @return A list of groups which override the given texture + */ + Placements set_texture( const SGPropertyNode* placement, + osg::Texture2D* new_texture, + osg::NodeCallback* cull_callback = 0 ); /** * Get the OSG camera for drawing this gauge. @@ -67,17 +135,32 @@ public: osg::Camera* getCamera() { return camera.get(); } osg::Texture2D* getTexture() { return texture.get(); } - void setTexture(osg::Texture2D* t) { texture = t; } + //void setTexture(osg::Texture2D* t) { texture = t; } // Real initialization function. Bad name. - void allocRT(void); + void allocRT(osg::NodeCallback* camera_cull_callback = 0); private: - int textureWH; + int _size_x, + _size_y, + _view_width, + _view_height; + bool _use_image_coords, + _use_stencil, + _use_mipmapping; + + // Multisampling parameters + int _coverage_samples, + _color_samples; + bool rtAvailable; osg::ref_ptr camera; osg::ref_ptr texture; + void updateCoordinateFrame(); + void updateStencil(); + void updateSampling(); + }; #endif // _OD_GAUGE_HXX diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 5f5b5360e..ae57786a6 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -86,6 +86,7 @@ #include #include +#include #include #include #include @@ -1158,6 +1159,11 @@ bool fgInitSubsystems() { //////////////////////////////////////////////////////////////////// fgGetBool("/sim/rendering/bump-mapping", false); + //////////////////////////////////////////////////////////////////// + // Initialize the canvas 2d drawing subsystem. + //////////////////////////////////////////////////////////////////// + globals->add_subsystem("Canvas2D", new CanvasMgr, SGSubsystemMgr::DISPLAY); + //////////////////////////////////////////////////////////////////// // Initialise the ATIS Manager // Note that this is old stuff, but is necessary for the diff --git a/src/Main/globals.cxx b/src/Main/globals.cxx index 1de5ecc7a..8c8ca52bf 100644 --- a/src/Main/globals.cxx +++ b/src/Main/globals.cxx @@ -335,6 +335,12 @@ SGPath FGGlobals::resolve_maybe_aircraft_path(const std::string& branch) const return simgear::ResourceManager::instance()->findPath(branch); } +SGPath FGGlobals::resolve_ressource_path(const std::string& branch) const +{ + return simgear::ResourceManager::instance() + ->findPath(branch, SGPath(fgGetString("/sim/aircraft-dir"))); +} + FGRenderer * FGGlobals::get_renderer () const { diff --git a/src/Main/globals.hxx b/src/Main/globals.hxx index 8600e46db..2ec9cc377 100644 --- a/src/Main/globals.hxx +++ b/src/Main/globals.hxx @@ -215,6 +215,15 @@ public: */ SGPath resolve_maybe_aircraft_path(const std::string& branch) const; + /** + * Search in the following directories: + * + * 1. Root directory of current aircraft (defined by /sim/aircraft-dir) + * 2. All aircraft directories if branch starts with Aircraft/ + * 3. fg_data directory + */ + SGPath resolve_ressource_path(const std::string& branch) const; + inline const std::string &get_browser () const { return browser; } void set_browser (const std::string &b) { browser = b; }