]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/od_gauge.cxx
c6be269e4e5e5e3074c4b1d0d562f92914c92d36
[flightgear.git] / src / Cockpit / od_gauge.cxx
1 // Owner Drawn Gauge helper class
2 //
3 // Written by Harald JOHNSEN, started May 2005.
4 //
5 // Copyright (C) 2005  Harald JOHNSEN
6 //
7 // Ported to OSG by Tim Moore - Jun 2007
8 //
9 // Heavily modified to be usable for the 2d Canvas by Thomas Geymayer - April 2012
10 // Supports now multisampling/mipmapping, usage of the stencil buffer and placing
11 // the texture in the scene by certain filter criteria
12 //
13 // This program is free software; you can redistribute it and/or
14 // modify it under the terms of the GNU General Public License as
15 // published by the Free Software Foundation; either version 2 of the
16 // License, or (at your option) any later version.
17 //
18 // This program is distributed in the hope that it will be useful, but
19 // WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 // General Public License for more details.
22 //
23 // You should have received a copy of the GNU General Public License
24 // along with this program; if not, write to the Free Software
25 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
26 //
27 //
28
29 #ifdef HAVE_CONFIG_H
30 #  include "config.h"
31 #endif
32
33 #include <osg/Texture2D>
34 #include <osg/AlphaFunc>
35 #include <osg/BlendFunc>
36 #include <osg/Camera>
37 #include <osg/Geode>
38 #include <osg/NodeVisitor>
39 #include <osg/Matrix>
40 #include <osg/PolygonMode>
41 #include <osg/ShadeModel>
42 #include <osg/StateSet>
43 #include <osg/FrameBufferObject> // for GL_DEPTH_STENCIL_EXT on Windows
44
45 #include <osgDB/FileNameUtils>
46
47 #include <simgear/scene/material/EffectGeode.hxx>
48 #include <simgear/scene/util/RenderConstants.hxx>
49
50 #include <Main/globals.hxx>
51 #include <Viewer/renderer.hxx>
52 #include <Scenery/scenery.hxx>
53 #include "od_gauge.hxx"
54
55 #include <cassert>
56
57 //------------------------------------------------------------------------------
58 FGODGauge::FGODGauge():
59   _size_x( -1 ),
60   _size_y( -1 ),
61   _view_width( -1 ),
62   _view_height( -1 ),
63   _use_image_coords( false ),
64   _use_stencil( false ),
65   _use_mipmapping( false ),
66   _coverage_samples( 0 ),
67   _color_samples( 0 ),
68   rtAvailable( false )
69 {
70 }
71
72 //------------------------------------------------------------------------------
73 FGODGauge::~FGODGauge()
74 {
75   if( camera.valid() )
76     globals->get_renderer()->removeCamera(camera.get());
77 }
78
79 //------------------------------------------------------------------------------
80 void FGODGauge::setSize(int size_x, int size_y)
81 {
82   _size_x = size_x;
83   _size_y = size_y < 0 ? size_x : size_y;
84
85   if( texture.valid() )
86     texture->setTextureSize(_size_x, _size_x);
87 }
88
89 //----------------------------------------------------------------------------
90 void FGODGauge::setViewSize(int width, int height)
91 {
92   _view_width = width;
93   _view_height = height < 0 ? width : height;
94
95   if( camera )
96     updateCoordinateFrame();
97 }
98
99 //------------------------------------------------------------------------------
100 void FGODGauge::useImageCoords(bool use)
101 {
102   if( use == _use_image_coords )
103     return;
104
105   _use_image_coords = use;
106
107   if( texture )
108     updateCoordinateFrame();
109 }
110
111 //------------------------------------------------------------------------------
112 void FGODGauge::useStencil(bool use)
113 {
114   if( use == _use_stencil )
115     return;
116
117   _use_stencil = use;
118
119   if( texture )
120     updateStencil();
121 }
122
123 //------------------------------------------------------------------------------
124 void FGODGauge::setSampling( bool mipmapping,
125                              int coverage_samples,
126                              int color_samples )
127 {
128   if(    _use_mipmapping == mipmapping
129       && _coverage_samples == coverage_samples
130       && _color_samples == color_samples )
131     return;
132
133   _use_mipmapping = mipmapping;
134
135   if( color_samples > coverage_samples )
136   {
137     SG_LOG
138     (
139       SG_GL,
140       SG_WARN,
141       "FGODGauge::setSampling: color_samples > coverage_samples not allowed!"
142     );
143     color_samples = coverage_samples;
144   }
145
146   _coverage_samples = coverage_samples;
147   _color_samples = color_samples;
148
149   updateSampling();
150 }
151
152 //------------------------------------------------------------------------------
153 void FGODGauge::setRender(bool render)
154 {
155   // Only the far camera should trigger this texture to be rendered.
156   camera->setNodeMask(render ? simgear::BACKGROUND_BIT : 0);
157 }
158
159 //------------------------------------------------------------------------------
160 bool FGODGauge::serviceable(void) 
161 {
162   return rtAvailable;
163 }
164
165 //------------------------------------------------------------------------------
166 void FGODGauge::allocRT(osg::NodeCallback* camera_cull_callback)
167 {
168   camera = new osg::Camera;
169   camera->setDataVariance(osg::Object::DYNAMIC);
170   camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
171   camera->setRenderOrder(osg::Camera::PRE_RENDER);
172   camera->setClearColor(osg::Vec4(0.0f, 0.0f, 0.0f , 0.0f));
173   camera->setClearStencil(0);
174   camera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT,
175                                              osg::Camera::FRAME_BUFFER );
176
177   if( camera_cull_callback )
178     camera->setCullCallback(camera_cull_callback);
179
180   setRender(true);
181   updateCoordinateFrame();
182   updateStencil();
183
184   osg::StateSet* stateSet = camera->getOrCreateStateSet();
185   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
186   stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
187   stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
188   stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
189   stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK,
190           osg::PolygonMode::FILL),
191           osg::StateAttribute::ON);
192   stateSet->setAttributeAndModes(new osg::AlphaFunc(osg::AlphaFunc::GREATER,
193           0.0f),
194           osg::StateAttribute::ON);
195   stateSet->setAttribute(new osg::ShadeModel(osg::ShadeModel::FLAT));
196   stateSet->setAttributeAndModes(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA,
197           osg::BlendFunc::ONE_MINUS_SRC_ALPHA),
198           osg::StateAttribute::ON);
199   if( !texture )
200   {
201     texture = new osg::Texture2D;
202     texture->setTextureSize(_size_x, _size_y);
203     texture->setInternalFormat(GL_RGBA);
204   }
205
206   updateSampling();
207
208   globals->get_renderer()->addCamera(camera.get(), false);
209   rtAvailable = true;
210 }
211
212 //------------------------------------------------------------------------------
213 void FGODGauge::updateCoordinateFrame()
214 {
215   assert( camera );
216
217   if( _view_width < 0 )
218     _view_width = _size_x;
219   if( _view_height < 0 )
220     _view_height = _size_y;
221
222   camera->setViewport(0, 0, _size_x, _size_y);
223
224   if( _use_image_coords )
225     camera->setProjectionMatrix(
226       osg::Matrix::ortho2D(0, _view_width, _view_height, 0)
227     );
228   else
229     camera->setProjectionMatrix(
230       osg::Matrix::ortho2D( -_view_width/2.,  _view_width/2.,
231                             -_view_height/2., _view_height/2. )
232     );
233 }
234
235 //------------------------------------------------------------------------------
236 void FGODGauge::updateStencil()
237 {
238   assert( camera );
239
240   GLbitfield mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
241
242   if( _use_stencil)
243   {
244     camera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER,
245                      GL_DEPTH_STENCIL_EXT );
246     mask |= GL_STENCIL_BUFFER_BIT;
247   }
248   else
249   {
250     camera->detach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER);
251   }
252
253   camera->setClearMask(mask);
254 }
255
256 //------------------------------------------------------------------------------
257 void FGODGauge::updateSampling()
258 {
259   assert( camera );
260   assert( texture );
261
262   texture->setFilter(
263     osg::Texture2D::MIN_FILTER,
264     _use_mipmapping ? osg::Texture2D::LINEAR_MIPMAP_LINEAR
265                     : osg::Texture2D::LINEAR
266   );
267   camera->attach(
268     osg::Camera::COLOR_BUFFER,
269     texture.get(),
270     0, 0,
271     _use_mipmapping,
272     _coverage_samples,
273     _color_samples
274   );
275 }
276
277 /**
278  * Replace a texture in the airplane model with the gauge texture.
279  */
280 class ReplaceStaticTextureVisitor:
281   public osg::NodeVisitor
282 {
283   public:
284
285     ReplaceStaticTextureVisitor( const char* name,
286                                  osg::Texture2D* new_texture ):
287         osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
288         _tex_name( osgDB::getSimpleFileName(name) ),
289         _new_texture(new_texture)
290     {}
291
292     ReplaceStaticTextureVisitor( const SGPropertyNode* placement,
293                                  osg::Texture2D* new_texture,
294                                  osg::NodeCallback* cull_callback = 0 ):
295         osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
296         _tex_name( osgDB::getSimpleFileName(
297           placement->getStringValue("texture"))
298         ),
299         _node_name( placement->getStringValue("node") ),
300         _parent_name( placement->getStringValue("parent") ),
301         _new_texture(new_texture),
302         _cull_callback(cull_callback)
303     {
304       if(    _tex_name.empty()
305           && _node_name.empty()
306           && _parent_name.empty() )
307         SG_LOG
308         (
309           SG_GL,
310           SG_WARN,
311           "No filter criterion for replacing texture. "
312           " Every texture will be replaced!"
313         );
314     }
315
316     /**
317      * Get a list of groups which have been inserted into the scene graph to
318      * replace the given texture
319      */
320     canvas::Placements& getPlacements()
321     {
322       return _placements;
323     }
324
325     virtual void apply(osg::Geode& node)
326     {
327       simgear::EffectGeode* eg = dynamic_cast<simgear::EffectGeode*>(&node);
328       if( !eg )
329         return;
330
331       osg::StateSet* ss = eg->getEffect()->getDefaultStateSet();
332       if( !ss )
333         return;
334
335       osg::Group *parent = node.getParent(0);
336       if( !_node_name.empty() && parent->getName() != _node_name )
337         return;
338
339       if( !_parent_name.empty() )
340       {
341         // Traverse nodes upwards starting at the parent node (skip current
342         // node)
343         const osg::NodePath& np = getNodePath();
344         bool found = false;
345         for( int i = static_cast<int>(np.size()) - 2; i >= 0; --i )
346         {
347           const osg::Node* path_segment = np[i];
348           const osg::Node* path_parent = path_segment->getParent(0);
349
350           // A node without a name is always the parent of the root node of
351           // the model just containing the file name
352           if( path_parent && path_parent->getName().empty() )
353             return;
354
355           if( path_segment->getName() == _parent_name )
356           {
357             found = true;
358             break;
359           }
360         }
361
362         if( !found )
363           return;
364       }
365
366       for( size_t unit = 0; unit < ss->getNumTextureAttributeLists(); ++unit )
367       {
368         osg::Texture2D* tex = dynamic_cast<osg::Texture2D*>
369         (
370           ss->getTextureAttribute(unit, osg::StateAttribute::TEXTURE)
371         );
372
373         if( !tex || !tex->getImage() || tex == _new_texture )
374           continue;
375
376         if( !_tex_name.empty() )
377         {
378           std::string tex_name = tex->getImage()->getFileName();
379           std::string tex_name_simple = osgDB::getSimpleFileName(tex_name);
380           if( !osgDB::equalCaseInsensitive(_tex_name, tex_name_simple) )
381             continue;
382         }
383
384         // insert a new group between the geode an it's parent which overrides
385         // the texture
386         osg::ref_ptr<osg::Group> group = new osg::Group;
387         group->setName("canvas texture group");
388         group->addChild(eg);
389         parent->removeChild(eg);
390         parent->addChild(group);
391
392         if( _cull_callback )
393           group->setCullCallback(_cull_callback);
394
395         _placements.push_back(
396           canvas::PlacementPtr(new ObjectPlacement(group))
397         );
398
399         osg::StateSet* stateSet = group->getOrCreateStateSet();
400         stateSet->setTextureAttribute( unit, _new_texture,
401                                              osg::StateAttribute::OVERRIDE );
402         stateSet->setTextureMode( unit, GL_TEXTURE_2D,
403                                         osg::StateAttribute::ON );
404
405         SG_LOG
406         (
407           SG_GL,
408           SG_INFO,
409              "Replaced texture '" << _tex_name << "'"
410           << " for object '" << parent->getName() << "'"
411           << (!_parent_name.empty() ? " with parent '" + _parent_name + "'"
412                                     : "")
413         );
414         return;
415       }
416     }
417
418   protected:
419
420     class ObjectPlacement:
421       public canvas::Placement
422     {
423       public:
424         ObjectPlacement(osg::ref_ptr<osg::Group> group):
425           _group(group)
426         {}
427
428         /**
429          * Remove placement from the scene
430          */
431         virtual ~ObjectPlacement()
432         {
433           assert( _group->getNumChildren() == 1 );
434           osg::Node *child = _group->getChild(0);
435
436           if( _group->getNumParents() )
437           {
438             osg::Group *parent = _group->getParent(0);
439             parent->addChild(child);
440             parent->removeChild(_group);
441           }
442
443           _group->removeChild(child);
444         }
445
446       private:
447         osg::ref_ptr<osg::Group> _group;
448     };
449
450     std::string _tex_name,      ///<! Name of texture to be replaced
451                 _node_name,     ///<! Only replace if node name matches
452                 _parent_name;   ///<! Only replace if any parent node matches
453                                 ///   given name (all the tree upwards)
454     osg::Texture2D     *_new_texture;
455     osg::NodeCallback  *_cull_callback;
456
457     canvas::Placements _placements;
458 };
459
460 //------------------------------------------------------------------------------
461 canvas::Placements FGODGauge::set_texture( const char* name,
462                                            osg::Texture2D* new_texture )
463 {
464   osg::Group* root = globals->get_scenery()->get_aircraft_branch();
465   ReplaceStaticTextureVisitor visitor(name, new_texture);
466   root->accept(visitor);
467   return visitor.getPlacements();
468 }
469
470 //------------------------------------------------------------------------------
471 canvas::Placements FGODGauge::set_texture( const SGPropertyNode* placement,
472                                            osg::Texture2D* new_texture,
473                                            osg::NodeCallback* cull_callback )
474 {
475   osg::Group* root = globals->get_scenery()->get_aircraft_branch();
476   ReplaceStaticTextureVisitor visitor(placement, new_texture, cull_callback);
477   root->accept(visitor);
478   return visitor.getPlacements();
479 }