]> git.mxchange.org Git - flightgear.git/blob - src/Model/panelnode.cxx
Fix an (unlikely) startup crash
[flightgear.git] / src / Model / panelnode.cxx
1 #ifdef HAVE_CONFIG_H
2 #  include <config.h>
3 #endif
4
5 #include "panelnode.hxx"
6
7 #include <vector>
8 #include <algorithm>
9
10 #include <osg/Geode>
11 #include <osg/Switch>
12 #include <osg/BlendFunc>
13
14 #include <simgear/compiler.h>
15 #include <simgear/structure/exception.hxx>
16 #include <simgear/debug/logstream.hxx>
17
18 #include <simgear/scene/util/OsgMath.hxx>
19 #include <simgear/scene/util/SGPickCallback.hxx>
20 #include <simgear/scene/util/SGSceneUserData.hxx>
21 #include <simgear/scene/util/SGNodeMasks.hxx>
22
23 #include <plib/pu.h>
24
25 #include <Main/fg_os.hxx>
26 #include <Cockpit/panel.hxx>
27 #include <Cockpit/panel_io.hxx>
28 #include "Viewer/viewer.hxx"
29 #include "Viewer/viewmgr.hxx"
30
31 using std::vector;
32
33 class PanelTransformListener : public SGPropertyChangeListener
34 {
35 public:
36     PanelTransformListener(FGPanelNode* pn) : _panelNode(pn) {}
37     
38     virtual void valueChanged (SGPropertyNode * node)
39     {
40         _panelNode->dirtyBound();
41     }
42 private:
43     FGPanelNode* _panelNode;
44 };
45
46 class PanelPathListener : public SGPropertyChangeListener
47 {
48 public:
49     PanelPathListener(FGPanelNode* pn) : _panelNode(pn) {}
50     
51     virtual void valueChanged (SGPropertyNode * node)
52     {
53         _panelNode->setPanelPath(node->getStringValue());
54     }
55 private:
56     FGPanelNode* _panelNode;
57 };
58
59 class FGPanelPickCallback : public SGPickCallback {
60 public:
61   FGPanelPickCallback(FGPanelNode* p) :
62     panel(p)
63   {}
64   
65   virtual bool buttonPressed( int b,
66                               const osgGA::GUIEventAdapter&,
67                               const Info& info )
68   {
69       if (!panel->getPanel()) {
70           return false;
71       }
72       
73     button = b;
74   // convert to panel coordinates
75     osg::Matrixd m = osg::Matrixd::inverse(panel->transformMatrix());
76     picked = toOsg(info.local) * m;
77     SG_LOG( SG_INSTR, SG_DEBUG, "panel pick: " << toSG(picked) );
78   
79   // send to the panel
80     return panel->getPanel()->doLocalMouseAction(button, MOUSE_BUTTON_DOWN, 
81                                           picked.x(), picked.y());
82   }
83   
84   virtual void update(double dt, int keyModState)
85   {
86     panel->getPanel()->updateMouseDelay(dt);
87   }
88   
89   virtual void buttonReleased( int,
90                                const osgGA::GUIEventAdapter&,
91                                const Info* )
92   {
93     panel->getPanel()->doLocalMouseAction(button, MOUSE_BUTTON_UP, 
94                                           picked.x(), picked.y());
95   }
96   
97 private:
98   FGPanelNode* panel;
99   int button;
100   osg::Vec3 picked;
101 };
102
103 class FGPanelSwitchCallback : public osg::NodeCallback {
104 public:
105   FGPanelSwitchCallback(FGPanelNode* pn) : 
106     panel(pn),
107     visProp(fgGetNode("/sim/panel/visibility"))
108   {
109   }
110   
111   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
112   {
113     assert(dynamic_cast<osg::Switch*>(node));
114     osg::Switch* sw = static_cast<osg::Switch*>(node);
115     
116     if (!visProp->getBoolValue()) {
117       sw->setValue(0, false);
118       return;
119     }
120   
121     
122     panel->lazyLoad(); // isVisible check needs the panel loaded for auto-hide flag
123     bool enabled = panel->isVisible2d();
124     sw->setValue(0, enabled);
125     if (!enabled)
126       return;
127     
128     traverse(node, nv);
129   }
130   
131 private:
132   FGPanelNode* panel;
133   SGPropertyNode_ptr visProp;
134 };
135
136
137 FGPanelNode::FGPanelNode(SGPropertyNode* props) :
138     _is2d(false),
139     _resizeToViewport(false),
140     _listener(NULL)
141 {  
142   commonInit();
143   _panelPath = props->getStringValue("path");
144
145   // And the corner points
146   SGPropertyNode* pt = props->getChild("bottom-left");
147   _bottomLeft[0] = pt->getFloatValue("x-m");
148   _bottomLeft[1] = pt->getFloatValue("y-m");
149   _bottomLeft[2] = pt->getFloatValue("z-m");
150   
151   pt = props->getChild("top-left");
152   _topLeft[0] = pt->getFloatValue("x-m");
153   _topLeft[1] = pt->getFloatValue("y-m");
154   _topLeft[2] = pt->getFloatValue("z-m");
155   
156   pt = props->getChild("bottom-right");
157   _bottomRight[0] = pt->getFloatValue("x-m");
158   _bottomRight[1] = pt->getFloatValue("y-m");
159   _bottomRight[2] = pt->getFloatValue("z-m");
160   
161   _depthTest = props->getBoolValue("depth-test");
162 }
163
164 FGPanelNode::FGPanelNode() :
165     _is2d(true),
166   _resizeToViewport(true),
167   _depthTest(false)
168 {
169     globals->get_commands()->addCommand("panel-mouse-click", this, &FGPanelNode::panelMouseClickCommand);
170
171     SGPropertyNode* pathNode = fgGetNode("/sim/panel/path");
172     _pathListener.reset(new PanelPathListener(this));
173     pathNode->addChangeListener(_pathListener.get());
174     setPanelPath(pathNode->getStringValue());
175     
176     // for a 2D panel, various options adjust the transformation
177     // matrix. We need to pass this data on to OSG or its bounding box
178     // will be stale, and picking will break.
179     // http://code.google.com/p/flightgear-bugs/issues/detail?id=864
180     _listener = new PanelTransformListener(this);
181     fgGetNode("/sim/panel/x-offset", true)->addChangeListener(_listener);
182     fgGetNode("/sim/panel/y-offset", true)->addChangeListener(_listener);
183     fgGetNode("/sim/startup/xsize", true)->addChangeListener(_listener);
184     fgGetNode("/sim/startup/ysize", true)->addChangeListener(_listener);
185     
186     commonInit();
187 }
188
189 FGPanelNode::~FGPanelNode()
190 {
191     if (_is2d) {
192         globals->get_commands()->removeCommand("panel-mouse-click");
193         SGPropertyNode* pathNode = fgGetNode("/sim/panel/path");
194         pathNode->removeChangeListener(_pathListener.get());
195     }
196     
197     if (_listener) {
198         fgGetNode("/sim/panel/x-offset", true)->removeChangeListener(_listener);
199         fgGetNode("/sim/panel/y-offset", true)->removeChangeListener(_listener);
200         fgGetNode("/sim/startup/xsize", true)->removeChangeListener(_listener);
201         fgGetNode("/sim/startup/ysize", true)->removeChangeListener(_listener);
202         delete _listener;
203     }
204 }
205
206 void FGPanelNode::setPanelPath(const std::string& panel)
207 {
208   if (panel == _panelPath) {
209     return;
210   }
211   
212   _panelPath = panel;
213   if (_panel) {
214     _panel.clear();
215   }
216 }
217
218 void FGPanelNode::lazyLoad()
219 {
220   if (!_panelPath.empty() && !_panel) {
221     _panel = fgReadPanel(_panelPath);
222     if (!_panel) {
223       SG_LOG(SG_COCKPIT, SG_WARN, "failed to read panel from:" << _panelPath);
224       _panelPath = std::string(); // don't keep trying to read
225       return;
226     }
227     
228     _panel->setDepthTest(_depthTest);
229     initWithPanel();
230   }
231 }
232
233 void FGPanelNode::commonInit()
234 {
235   setUseDisplayList(false);
236   setDataVariance(Object::DYNAMIC);
237   getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
238   getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON);
239 }
240
241 void FGPanelNode::initWithPanel()
242 {
243   int i;
244
245   // Read out the pixel-space info
246   float panelWidth = _panel->getWidth();
247   float panelHeight = _panel->getHeight();
248
249   _panel->getLogicalExtent(_xmin, _ymin, _xmax, _ymax);
250     
251   // Now generate our transformation matrix.  For shorthand, use
252   // "a", "b", and "c" as our corners and "m" as the matrix. The
253   // vector u goes from a to b, v from a to c, and w is a
254   // perpendicular cross product.
255   osg::Vec3 a = _bottomLeft;
256   osg::Vec3 b = _bottomRight;
257   osg::Vec3 c = _topLeft;
258   osg::Vec3 u = b - a;
259   osg::Vec3 v = c - a;
260   osg::Vec3 w = u^v;
261
262     osg::Matrix& m = _xform;
263     if ((u.length2() == 0.0) || (b.length2() == 0.0)) {
264         m.makeIdentity();
265     } else {
266         // Now generate a trivial basis transformation matrix.  If we want
267         // to map the three unit vectors to three arbitrary vectors U, V,
268         // and W, then those just become the columns of the 3x3 matrix.
269         m(0,0) = u[0]; m(1,0) = v[0]; m(2,0) = w[0]; m(3,0) = a[0];//    |Ux Vx Wx|
270         m(0,1) = u[1]; m(1,1) = v[1]; m(2,1) = w[1]; m(3,1) = a[1];//m = |Uy Vy Wy|
271         m(0,2) = u[2]; m(1,2) = v[2]; m(2,2) = w[2]; m(3,2) = a[2];//    |Uz Vz Wz|
272         m(0,3) = 0;    m(1,3) = 0;    m(2,3) = 0;    m(3,3) = 1;
273     }
274
275   // The above matrix maps the unit (!) square to the panel
276   // rectangle.  Postmultiply scaling factors that match the
277   // pixel-space size of the panel.
278   for(i=0; i<4; ++i) {
279       m(0,i) *= 1.0/panelWidth;
280       m(1,i) *= 1.0/panelHeight;
281   }
282
283   dirtyBound();
284 }
285
286 osg::Matrix FGPanelNode::transformMatrix() const
287 {
288   if (!_panel) {
289     return osg::Matrix();
290   }
291
292   if (!_resizeToViewport) {
293     return _xform;
294   }
295   
296   double s = _panel->getAspectScale();
297   osg::Matrix m = osg::Matrix::scale(s, s, 1.0);
298   m *= osg::Matrix::translate(_panel->getXOffset(), _panel->getYOffset(), 0.0);
299
300   return m;
301 }
302
303 void
304 FGPanelNode::drawImplementation(osg::State& state) const
305 {  
306   if (!_panel) {
307     return;
308   }
309   
310   osg::ref_ptr<osg::RefMatrix> mv = new osg::RefMatrix;
311   mv->set(transformMatrix() * state.getModelViewMatrix());
312   state.applyModelViewMatrix(mv.get());
313   
314   _panel->draw(state);
315 }
316
317 osg::BoundingBox
318 FGPanelNode::computeBound() const
319 {
320
321   osg::Vec3 coords[3];
322   osg::Matrix m(transformMatrix());
323   coords[0] = m.preMult(osg::Vec3(_xmin,_ymin,0));
324   coords[1] = m.preMult(osg::Vec3(_xmax,_ymin,0));
325   coords[2] = m.preMult(osg::Vec3(_xmin,_ymax,0));
326
327   osg::BoundingBox bb;
328   bb.expandBy(coords[0]);
329   bb.expandBy(coords[1]);
330   bb.expandBy(coords[2]);
331   return bb;
332 }
333
334 void FGPanelNode::accept(osg::PrimitiveFunctor& functor) const
335 {
336   osg::Vec3 coords[4];
337   osg::Matrix m(transformMatrix());
338   
339   coords[0] = m.preMult(osg::Vec3(_xmin,_ymin,0));
340   coords[1] = m.preMult(osg::Vec3(_xmax,_ymin,0));
341   coords[2] = m.preMult(osg::Vec3(_xmax, _ymax, 0));
342   coords[3] = m.preMult(osg::Vec3(_xmin,_ymax,0));
343
344   functor.setVertexArray(4, coords);
345   functor.drawArrays( GL_QUADS, 0, 4);
346 }
347
348 bool FGPanelNode::isVisible2d() const
349 {
350   if (!_panel) {
351     return false;
352   }
353   
354   if (!_hideNonDefaultViews) {
355     _hideNonDefaultViews = fgGetNode("/sim/panel/hide-nonzero-view", true);
356   }
357   
358   if (_hideNonDefaultViews->getBoolValue()) {
359     if (globals->get_viewmgr()->get_current() != 0) {
360       return false;
361     }
362   }
363   
364   if (!_autoHide2d) {
365     _autoHide2d = fgGetNode("/sim/panel/hide-nonzero-heading-offset", true);
366   }
367   
368   if (_panel->getAutohide() && _autoHide2d->getBoolValue()) {
369     if (!globals->get_current_view()) {
370       return false;
371     }
372     
373     return globals->get_current_view()->getHeadingOffset_deg() == 0;
374   }
375   
376   return true;
377 }
378
379 static osg::Node* createGeode(FGPanelNode* panel)
380 {
381     osg::Geode* geode = new osg::Geode;
382     geode->addDrawable(panel);
383     
384     geode->setNodeMask(SG_NODEMASK_PICK_BIT | SG_NODEMASK_2DPANEL_BIT);
385     
386     SGSceneUserData* userData;
387     userData = SGSceneUserData::getOrCreateSceneUserData(geode);
388     userData->setPickCallback(new FGPanelPickCallback(panel));
389     return geode;
390 }
391
392 osg::Node* FGPanelNode::create2DPanelNode()
393 {
394   FGPanelNode* drawable = new FGPanelNode;
395     
396   osg::Switch* ps = new osg::Switch;
397   osg::StateSet* stateSet = ps->getOrCreateStateSet();
398   stateSet->setRenderBinDetails(1000, "RenderBin");
399   ps->addChild(createGeode(drawable));
400   
401   // speed optimization?
402   stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
403   stateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA));
404   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
405   stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
406   stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
407   
408   ps->setUpdateCallback(new FGPanelSwitchCallback(drawable));
409   return ps;
410 }
411
412 osg::Node* FGPanelNode::load(SGPropertyNode *n)
413 {
414   FGPanelNode* drawable = new FGPanelNode(n);
415   drawable->lazyLoad(); // force load now for 2.5D panels
416   return createGeode(drawable);
417 }
418
419 /**
420  * Built-in command: pass a mouse click to the panel.
421  *
422  * button: the mouse button number, zero-based.
423  * is-down: true if the button is down, false if it is up.
424  * x-pos: the x position of the mouse click.
425  * y-pos: the y position of the mouse click.
426  */
427 bool
428 FGPanelNode::panelMouseClickCommand(const SGPropertyNode * arg)
429 {
430     return _panel->doMouseAction(arg->getIntValue("button"),
431                          arg->getBoolValue("is-down") ? PU_DOWN : PU_UP,
432                          arg->getIntValue("x-pos"),
433                          arg->getIntValue("y-pos"));
434 }