]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/panel.cxx
#738: crash when switching 2D panels
[flightgear.git] / src / Cockpit / panel.cxx
1 //  panel.cxx - default, 2D single-engine prop instrument panel
2 //
3 //  Written by David Megginson, started January 2000.
4 //
5 //  This program is free software; you can redistribute it and/or
6 //  modify it under the terms of the GNU General Public License as
7 //  published by the Free Software Foundation; either version 2 of the
8 //  License, or (at your option) any later version.
9 // 
10 //  This program is distributed in the hope that it will be useful, but
11 //  WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 //  General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 //
19 //  $Id$
20
21 //JVK
22 // On 2D panels all instruments include light sources were in night displayed
23 // with a red mask (instrument light). It is not correct for light sources
24 // (bulbs). There is added new layer property "emissive" (boolean) (only for
25 // textured layers).
26 // If a layer has to shine set it in the "instrument_def_file.xml" inside the
27 // <layer> tag by adding <emissive>true</emissive> tag. When omitted the default
28 // value is for backward compatibility set to false.
29
30 #ifdef HAVE_CONFIG_H
31 #  include <config.h>
32 #endif
33
34 #include "panel.hxx"
35
36 #include <stdio.h>      // sprintf
37 #include <string.h>
38 #include <iostream>
39 #include <cassert>
40
41 #include <osg/CullFace>
42 #include <osg/Depth>
43 #include <osg/Material>
44 #include <osg/Matrixf>
45 #include <osg/TexEnv>
46 #include <osg/PolygonOffset>
47
48 #include <simgear/compiler.h>
49
50 #include <plib/fnt.h>
51
52 #include <boost/foreach.hpp>
53 #include <simgear/debug/logstream.hxx>
54 #include <simgear/misc/sg_path.hxx>
55 #include <simgear/scene/model/model.hxx>
56 #include <osg/GLU>
57
58 #include <Main/globals.hxx>
59 #include <Main/fg_props.hxx>
60 #include <Main/viewmgr.hxx>
61 #include <Time/light.hxx>
62 #include <GUI/FGFontCache.hxx>  
63 #include <Main/viewer.hxx>
64 #include <Instrumentation/dclgps.hxx>
65
66 #define WIN_X 0
67 #define WIN_Y 0
68 #define WIN_W 1024
69 #define WIN_H 768
70
71 // The number of polygon-offset "units" to place between layers.  In
72 // principle, one is supposed to be enough.  In practice, I find that
73 // my hardware/driver requires many more.
74 #define POFF_UNITS 8
75
76 using std::map;
77
78 ////////////////////////////////////////////////////////////////////////
79 // Local functions.
80 ////////////////////////////////////////////////////////////////////////
81
82
83 /**
84  * Calculate the aspect adjustment for the panel.
85  */
86 static float
87 get_aspect_adjust (int xsize, int ysize)
88 {
89   float ideal_aspect = float(WIN_W) / float(WIN_H);
90   float real_aspect = float(xsize) / float(ysize);
91   return (real_aspect / ideal_aspect);
92 }
93
94
95 \f
96 ////////////////////////////////////////////////////////////////////////
97 // Global functions.
98 ////////////////////////////////////////////////////////////////////////
99
100 bool
101 fgPanelVisible ()
102 {
103      const FGPanel* current = globals->get_current_panel();
104      if (current == 0)
105         return false;
106      if (current->getVisibility() == 0)
107         return false;
108      if (globals->get_viewmgr()->get_current() != 0)
109         return false;
110      if (current->getAutohide() && globals->get_current_view()->getHeadingOffset_deg() * SGD_DEGREES_TO_RADIANS != 0)
111         return false;
112      return true;
113 }
114
115 \f
116 ////////////////////////////////////////////////////////////////////////
117 // Implementation of FGTextureManager.
118 ////////////////////////////////////////////////////////////////////////
119
120 map<string,osg::ref_ptr<osg::Texture2D> > FGTextureManager::_textureMap;
121
122 osg::Texture2D*
123 FGTextureManager::createTexture (const string &relativePath, bool staticTexture)
124 {
125   osg::Texture2D* texture = _textureMap[relativePath].get();
126   if (texture == 0) {
127     SGPath tpath = globals->resolve_aircraft_path(relativePath);
128     texture = SGLoadTexture2D(staticTexture, tpath);
129
130     _textureMap[relativePath] = texture;
131     if (!_textureMap[relativePath].valid()) 
132       SG_LOG( SG_COCKPIT, SG_ALERT, "Texture *still* doesn't exist" );
133     SG_LOG( SG_COCKPIT, SG_DEBUG, "Created texture " << relativePath );
134   }
135
136   return texture;
137 }
138
139
140 void FGTextureManager::addTexture(const string &relativePath,
141                                   osg::Texture2D* texture)
142 {
143     _textureMap[relativePath] = texture;
144 }
145 \f
146 ////////////////////////////////////////////////////////////////////////
147 // Implementation of FGCropped Texture.
148 ////////////////////////////////////////////////////////////////////////
149
150
151 FGCroppedTexture::FGCroppedTexture ()
152   : _path(""), _texture(0),
153     _minX(0.0), _minY(0.0), _maxX(1.0), _maxY(1.0)
154 {
155 }
156
157
158 FGCroppedTexture::FGCroppedTexture (const string &path,
159                                     float minX, float minY,
160                                     float maxX, float maxY)
161   : _path(path), _texture(0),
162     _minX(minX), _minY(minY), _maxX(maxX), _maxY(maxY)
163 {
164 }
165
166
167 FGCroppedTexture::~FGCroppedTexture ()
168 {
169 }
170
171
172 osg::StateSet*
173 FGCroppedTexture::getTexture ()
174 {
175   if (_texture == 0) {
176     _texture = new osg::StateSet;
177     _texture->setTextureAttribute(0, FGTextureManager::createTexture(_path));
178     _texture->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
179     _texture->setTextureAttribute(0, new osg::TexEnv(osg::TexEnv::MODULATE));
180   }
181   return _texture.get();
182 }
183
184
185 \f
186 ////////////////////////////////////////////////////////////////////////
187 // Implementation of FGPanel.
188 ////////////////////////////////////////////////////////////////////////
189
190 static fntRenderer text_renderer;
191 static sgVec4 panel_color;
192 static sgVec4 emissive_panel_color = {1,1,1,1};
193
194 /**
195  * Constructor.
196  */
197 FGPanel::FGPanel ()
198   : _mouseDown(false),
199     _mouseInstrument(0),
200     _width(WIN_W), _height(int(WIN_H * 0.5768 + 1)),
201  //   _view_height(int(WIN_H * 0.4232)),
202     _visibility(fgGetNode("/sim/panel/visibility", true)),
203     _x_offset(fgGetNode("/sim/panel/x-offset", true)),
204     _y_offset(fgGetNode("/sim/panel/y-offset", true)),
205     _jitter(fgGetNode("/sim/panel/jitter", true)),
206     _flipx(fgGetNode("/sim/panel/flip-x", true)),
207     _xsize_node(fgGetNode("/sim/startup/xsize", true)),
208     _ysize_node(fgGetNode("/sim/startup/ysize", true)),
209     _enable_depth_test(false),
210     _drawPanelHotspots("/sim/panel-hotspots")
211 {
212 }
213
214
215 /**
216  * Destructor.
217  */
218 FGPanel::~FGPanel ()
219 {
220   for (instrument_list_type::iterator it = _instruments.begin();
221        it != _instruments.end();
222        it++) {
223     delete *it;
224     *it = 0;
225   }
226 }
227
228
229 /**
230  * Add an instrument to the panel.
231  */
232 void
233 FGPanel::addInstrument (FGPanelInstrument * instrument)
234 {
235   _instruments.push_back(instrument);
236 }
237
238
239 /**
240  * Initialize the panel.
241  */
242 void
243 FGPanel::init ()
244 {
245 }
246
247
248 /**
249  * Bind panel properties.
250  */
251 void
252 FGPanel::bind ()
253 {
254   fgSetArchivable("/sim/panel/visibility");
255   fgSetArchivable("/sim/panel/x-offset");
256   fgSetArchivable("/sim/panel/y-offset");
257   fgSetArchivable("/sim/panel/jitter");
258 }
259
260
261 /**
262  * Unbind panel properties.
263  */
264 void
265 FGPanel::unbind ()
266 {
267 }
268
269
270 void
271 FGPanel::update (double /* dt */)
272 {
273   updateMouseDelay();
274 }
275
276 double
277 FGPanel::getAspectScale() const
278 {
279   // set corner-coordinates correctly
280   
281   int xsize = _xsize_node->getIntValue();
282   int ysize = _ysize_node->getIntValue();
283   float aspect_adjust = get_aspect_adjust(xsize, ysize);
284   
285   if (aspect_adjust < 1.0)
286     return ysize / (double) WIN_H;
287   else
288     return xsize /(double) WIN_W;  
289 }
290
291 /**
292  * Handle repeatable mouse events.  Called from update() and from
293  * fgUpdate3DPanels().  This functionality needs to move into the
294  * input subsystem.  Counting a tick every two frames is clumsy...
295  */
296 void FGPanel::updateMouseDelay()
297 {
298     if (_mouseDown) {
299         _mouseDelay--;
300         if (_mouseDelay < 0) {
301             _mouseInstrument->doMouseAction(_mouseButton, 0, _mouseX, _mouseY);
302             _mouseDelay = 2;
303         }
304     }
305 }
306
307 void
308 FGPanel::draw(osg::State& state)
309 {
310     
311   // In 3D mode, it's possible that we are being drawn exactly on top
312   // of an existing polygon.  Use an offset to prevent z-fighting.  In
313   // 2D mode, this is a no-op.
314   static osg::ref_ptr<osg::StateSet> panelStateSet;
315   if (!panelStateSet.valid()) {
316     panelStateSet = new osg::StateSet;
317     panelStateSet->setAttributeAndModes(new osg::PolygonOffset(-1, -POFF_UNITS));
318     panelStateSet->setTextureAttribute(0, new osg::TexEnv);
319
320     // Draw the background
321     panelStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
322     panelStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
323     panelStateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
324     panelStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
325   
326     osg::Material* material = new osg::Material;
327     material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
328     material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(1, 1, 1, 1));
329     material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(1, 1, 1, 1));
330     material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4(0, 0, 0, 1));
331     material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4(0, 0, 0, 1));
332     panelStateSet->setAttribute(material);
333     
334     panelStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
335     panelStateSet->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));
336     panelStateSet->setAttributeAndModes(new osg::Depth(osg::Depth::LEQUAL));
337   }
338   if ( _enable_depth_test )
339     panelStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON);
340   else
341     panelStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
342   state.pushStateSet(panelStateSet.get());
343   state.apply();
344   state.setActiveTextureUnit(0);
345   state.setClientActiveTextureUnit(0);
346
347   FGLight *l = (FGLight *)(globals->get_subsystem("lighting"));
348   sgCopyVec4( panel_color, l->scene_diffuse().data());
349   if ( fgGetDouble("/systems/electrical/outputs/instrument-lights") > 1.0 ) {
350       if ( panel_color[0] < 0.7 ) panel_color[0] = 0.7;
351       if ( panel_color[1] < 0.2 ) panel_color[1] = 0.2;
352       if ( panel_color[2] < 0.2 ) panel_color[2] = 0.2;
353   }
354   glColor4fv( panel_color );
355   if (_bg != 0) {
356     state.pushStateSet(_bg.get());
357     state.apply();
358     state.setActiveTextureUnit(0);
359     state.setClientActiveTextureUnit(0);
360
361     glBegin(GL_POLYGON);
362     glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X, WIN_Y);
363     glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + _width, WIN_Y);
364     glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + _width, WIN_Y + _height);
365     glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X, WIN_Y + _height);
366     glEnd();
367     state.popStateSet();
368     state.apply();
369     state.setActiveTextureUnit(0);
370     state.setClientActiveTextureUnit(0);
371
372   } else {
373     for (int i = 0; i < 4; i ++) {
374       // top row of textures...(1,3,5,7)
375       state.pushStateSet(_mbg[i*2].get());
376       state.apply();
377       state.setActiveTextureUnit(0);
378       state.setClientActiveTextureUnit(0);
379
380       glBegin(GL_POLYGON);
381       glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + (_height/2));
382       glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + (_height/2));
383       glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + _height);
384       glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + _height);
385       glEnd();
386       state.popStateSet();
387       state.apply();
388       state.setActiveTextureUnit(0);
389       state.setClientActiveTextureUnit(0);
390
391       // bottom row of textures...(2,4,6,8)
392       state.pushStateSet(_mbg[i*2+1].get());
393       state.apply();
394       state.setActiveTextureUnit(0);
395       state.setClientActiveTextureUnit(0);
396
397       glBegin(GL_POLYGON);
398       glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y);
399       glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y);
400       glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + (_height/2));
401       glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + (_height/2));
402       glEnd();
403       state.popStateSet();
404       state.apply();
405       state.setActiveTextureUnit(0);
406       state.setClientActiveTextureUnit(0);
407
408     }
409   }
410
411   // Draw the instruments.
412   // Syd Adams: added instrument clipping
413   instrument_list_type::const_iterator current = _instruments.begin();
414   instrument_list_type::const_iterator end = _instruments.end();
415
416   GLdouble blx[4]={1.0,0.0,0.0,0.0};
417   GLdouble bly[4]={0.0,1.0,0.0,0.0};
418   GLdouble urx[4]={-1.0,0.0,0.0,0.0};
419   GLdouble ury[4]={0.0,-1.0,0.0,0.0};
420
421   for ( ; current != end; current++) {
422     FGPanelInstrument * instr = *current;
423     glPushMatrix();
424     glTranslated(instr->getXPos(), instr->getYPos(), 0);
425
426     int ix= instr->getWidth();
427     int iy= instr->getHeight();
428     glPushMatrix();
429     glTranslated(-ix/2,-iy/2,0);
430     glClipPlane(GL_CLIP_PLANE0,blx);
431     glClipPlane(GL_CLIP_PLANE1,bly);
432     glEnable(GL_CLIP_PLANE0);
433     glEnable(GL_CLIP_PLANE1);
434
435     glTranslated(ix,iy,0);
436     glClipPlane(GL_CLIP_PLANE2,urx);
437     glClipPlane(GL_CLIP_PLANE3,ury);
438     glEnable(GL_CLIP_PLANE2);
439     glEnable(GL_CLIP_PLANE3);
440     glPopMatrix();
441     instr->draw(state);
442
443     glPopMatrix();
444   }
445
446   glDisable(GL_CLIP_PLANE0);
447   glDisable(GL_CLIP_PLANE1);
448   glDisable(GL_CLIP_PLANE2);
449   glDisable(GL_CLIP_PLANE3);
450
451   state.popStateSet();
452   state.apply();
453   state.setActiveTextureUnit(0);
454   state.setClientActiveTextureUnit(0);
455
456
457   // Draw yellow "hotspots" if directed to.  This is a panel authoring
458   // feature; not intended to be high performance or to look good.
459   if ( _drawPanelHotspots ) {
460     static osg::ref_ptr<osg::StateSet> hotspotStateSet;
461     if (!hotspotStateSet.valid()) {
462       hotspotStateSet = new osg::StateSet;
463       hotspotStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF);
464       hotspotStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
465     }
466
467     state.pushStateSet(hotspotStateSet.get());
468     state.apply();
469     state.setActiveTextureUnit(0);
470     state.setClientActiveTextureUnit(0);
471
472
473     glPushAttrib(GL_ENABLE_BIT);
474     glDisable(GL_COLOR_MATERIAL);
475     glColor3f(1, 1, 0);
476     
477     for ( unsigned int i = 0; i < _instruments.size(); i++ )
478       _instruments[i]->drawHotspots(state);
479
480     glColor3f(0, 1, 1);
481
482     int x0, y0, x1, y1;
483     getLogicalExtent(x0, y0, x1, y1);
484     
485     glBegin(GL_LINE_LOOP);
486     glVertex2f(x0, y0);
487     glVertex2f(x1, y0);
488     glVertex2f(x1, y1);
489     glVertex2f(x0, y1);
490     glEnd();
491
492     
493     glPopAttrib();
494
495     state.popStateSet();
496     state.apply();
497     state.setActiveTextureUnit(0);
498     state.setClientActiveTextureUnit(0);
499
500   }
501 }
502
503 /**
504  * Set the panel's visibility.
505  */
506 void
507 FGPanel::setVisibility (bool visibility)
508 {
509   _visibility->setBoolValue( visibility );
510 }
511
512
513 /**
514  * Return true if the panel is visible.
515  */
516 bool
517 FGPanel::getVisibility () const
518 {
519   return _visibility->getBoolValue();
520 }
521
522
523 /**
524  * Set the panel's background texture.
525  */
526 void
527 FGPanel::setBackground (osg::Texture2D* texture)
528 {
529   osg::StateSet* stateSet = new osg::StateSet;
530   stateSet->setTextureAttribute(0, texture);
531   stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
532   stateSet->setTextureAttribute(0, new osg::TexEnv(osg::TexEnv::MODULATE));
533   _bg = stateSet;
534 }
535
536 /**
537  * Set the panel's multiple background textures.
538  */
539 void
540 FGPanel::setMultiBackground (osg::Texture2D* texture, int idx)
541 {
542   _bg = 0;
543
544   osg::StateSet* stateSet = new osg::StateSet;
545   stateSet->setTextureAttribute(0, texture);
546   stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
547   stateSet->setTextureAttribute(0, new osg::TexEnv(osg::TexEnv::MODULATE));
548   _mbg[idx] = stateSet;
549 }
550
551 /**
552  * Set the panel's x-offset.
553  */
554 void
555 FGPanel::setXOffset (int offset)
556 {
557   if (offset <= 0 && offset >= -_width + WIN_W)
558     _x_offset->setIntValue( offset );
559 }
560
561
562 /**
563  * Set the panel's y-offset.
564  */
565 void
566 FGPanel::setYOffset (int offset)
567 {
568   if (offset <= 0 && offset >= -_height)
569     _y_offset->setIntValue( offset );
570 }
571
572 /**
573  * Handle a mouse action in panel-local (not screen) coordinates.
574  * Used by the 3D panel code in Model/panelnode.cxx, in situations
575  * where the panel doesn't control its own screen location.
576  */
577 bool
578 FGPanel::doLocalMouseAction(int button, int updown, int x, int y)
579 {
580   // Note a released button and return
581   if (updown == 1) {
582     if (_mouseInstrument != 0)
583         _mouseInstrument->doMouseAction(_mouseButton, 1, _mouseX, _mouseY);
584     _mouseDown = false;
585     _mouseInstrument = 0;
586     return false;
587   }
588
589   // Search for a matching instrument.
590   for (int i = 0; i < (int)_instruments.size(); i++) {
591     FGPanelInstrument *inst = _instruments[i];
592     int ix = inst->getXPos();
593     int iy = inst->getYPos();
594     int iw = inst->getWidth() / 2;
595     int ih = inst->getHeight() / 2;
596     if (x >= ix - iw && x < ix + iw && y >= iy - ih && y < iy + ih) {
597       _mouseDown = true;
598       _mouseDelay = 20;
599       _mouseInstrument = inst;
600       _mouseButton = button;
601       _mouseX = x - ix;
602       _mouseY = y - iy;
603       // Always do the action once.
604       return _mouseInstrument->doMouseAction(_mouseButton, 0,
605                                              _mouseX, _mouseY);
606     }
607   }
608   return false;
609 }
610
611 /**
612  * Perform a mouse action.
613  */
614 bool
615 FGPanel::doMouseAction (int button, int updown, int x, int y)
616 {
617                                 // FIXME: this same code appears in update()
618   int xsize = _xsize_node->getIntValue();
619   int ysize = _ysize_node->getIntValue();
620   float aspect_adjust = get_aspect_adjust(xsize, ysize);
621
622                                 // Scale for the real window size.
623   if (aspect_adjust < 1.0) {
624     x = int(((float)x / xsize) * WIN_W * aspect_adjust);
625     y = int(WIN_H - ((float(y) / ysize) * WIN_H));
626   } else {
627     x = int(((float)x / xsize) * WIN_W);
628     y = int((WIN_H - ((float(y) / ysize) * WIN_H)) / aspect_adjust);
629   }
630
631                                 // Adjust for offsets.
632   x -= _x_offset->getIntValue();
633   y -= _y_offset->getIntValue();
634
635   // Having fixed up the coordinates, fall through to the local
636   // coordinate handler.
637   return doLocalMouseAction(button, updown, x, y);
638
639
640 void FGPanel::setDepthTest (bool enable) {
641     _enable_depth_test = enable;
642 }
643
644 class IntRect
645 {
646   
647 public:
648   IntRect() : 
649     x0(std::numeric_limits<int>::max()), 
650     y0(std::numeric_limits<int>::max()), 
651     x1(std::numeric_limits<int>::min()), 
652     y1(std::numeric_limits<int>::min()) 
653   { }
654   
655   IntRect(int x, int y, int w, int h) :
656     x0(x), y0(y), x1(x + w), y1( y + h)
657   { 
658     if (x1 < x0) {
659       std::swap(x0, x1);
660     }
661     
662     if (y1 < y0) {
663       std::swap(y0, y1);
664     }
665     
666     assert(x0 <= x1);
667     assert(y0 <= y1);
668   }
669   
670   void extend(const IntRect& r)
671   {
672     x0 = std::min(x0, r.x0);
673     y0 = std::min(y0, r.y0);
674     x1 = std::max(x1, r.x1);
675     y1 = std::max(y1, r.y1);
676   }
677   
678   int x0, y0, x1, y1;
679 };
680
681 void FGPanel::getLogicalExtent(int &x0, int& y0, int& x1, int &y1)
682 {  
683   IntRect result;
684   BOOST_FOREACH(FGPanelInstrument *inst, _instruments) {
685     inst->extendRect(result);
686   }
687   
688   x0 = result.x0;
689   y0 = result.y0;
690   x1 = result.x1;
691   y1 = result.y1;
692 }
693 \f
694 ////////////////////////////////////////////////////////////////////////.
695 // Implementation of FGPanelAction.
696 ////////////////////////////////////////////////////////////////////////
697
698 FGPanelAction::FGPanelAction ()
699 {
700 }
701
702 FGPanelAction::FGPanelAction (int button, int x, int y, int w, int h,
703                               bool repeatable)
704     : _button(button), _x(x), _y(y), _w(w), _h(h), _repeatable(repeatable)
705 {
706 }
707
708 FGPanelAction::~FGPanelAction ()
709 {
710   for (unsigned int i = 0; i < 2; i++) {
711       for (unsigned int j = 0; j < _bindings[i].size(); j++)
712           delete _bindings[i][j];
713   }
714 }
715
716 void
717 FGPanelAction::addBinding (SGBinding * binding, int updown)
718 {
719   _bindings[updown].push_back(binding);
720 }
721
722 bool
723 FGPanelAction::doAction (int updown)
724 {
725   if (test()) {
726     if ((updown != _last_state) || (updown == 0 && _repeatable)) {
727         int nBindings = _bindings[updown].size();
728         for (int i = 0; i < nBindings; i++)
729             _bindings[updown][i]->fire();
730     }
731     _last_state = updown;
732     return true;
733   } else {
734     return false;
735   }
736 }
737
738
739 \f
740 ////////////////////////////////////////////////////////////////////////
741 // Implementation of FGPanelTransformation.
742 ////////////////////////////////////////////////////////////////////////
743
744 FGPanelTransformation::FGPanelTransformation ()
745   : table(0)
746 {
747 }
748
749 FGPanelTransformation::~FGPanelTransformation ()
750 {
751   delete table;
752 }
753
754
755 \f
756 ////////////////////////////////////////////////////////////////////////
757 // Implementation of FGPanelInstrument.
758 ////////////////////////////////////////////////////////////////////////
759
760
761 FGPanelInstrument::FGPanelInstrument ()
762 {
763   setPosition(0, 0);
764   setSize(0, 0);
765 }
766
767 FGPanelInstrument::FGPanelInstrument (int x, int y, int w, int h)
768 {
769   setPosition(x, y);
770   setSize(w, h);
771 }
772
773 FGPanelInstrument::~FGPanelInstrument ()
774 {
775   for (action_list_type::iterator it = _actions.begin();
776        it != _actions.end();
777        it++) {
778     delete *it;
779     *it = 0;
780   }
781 }
782
783 void
784 FGPanelInstrument::drawHotspots(osg::State& state)
785 {
786   for ( unsigned int i = 0; i < _actions.size(); i++ ) {
787     FGPanelAction* a = _actions[i];
788     float x1 = getXPos() + a->getX();
789     float x2 = x1 + a->getWidth();
790     float y1 = getYPos() + a->getY();
791     float y2 = y1 + a->getHeight();
792
793     glBegin(GL_LINE_LOOP);
794     glVertex2f(x1, y1);
795     glVertex2f(x1, y2);
796     glVertex2f(x2, y2);
797     glVertex2f(x2, y1);
798     glEnd();
799   }
800 }
801
802 void
803 FGPanelInstrument::setPosition (int x, int y)
804 {
805   _x = x;
806   _y = y;
807 }
808
809 void
810 FGPanelInstrument::setSize (int w, int h)
811 {
812   _w = w;
813   _h = h;
814 }
815
816 int
817 FGPanelInstrument::getXPos () const
818 {
819   return _x;
820 }
821
822 int
823 FGPanelInstrument::getYPos () const
824 {
825   return _y;
826 }
827
828 int
829 FGPanelInstrument::getWidth () const
830 {
831   return _w;
832 }
833
834 int
835 FGPanelInstrument::getHeight () const
836 {
837   return _h;
838 }
839
840 void
841 FGPanelInstrument::extendRect(IntRect& r) const
842 {
843   IntRect instRect(_x, _y, _w, _h);
844   r.extend(instRect);
845   
846   BOOST_FOREACH(FGPanelAction* act, _actions) {
847     r.extend(IntRect(getXPos() + act->getX(), 
848                      getYPos() + act->getY(),
849                      act->getWidth(),
850                      act->getHeight()
851                      ));
852   }
853 }
854
855 void
856 FGPanelInstrument::addAction (FGPanelAction * action)
857 {
858   _actions.push_back(action);
859 }
860
861                                 // Coordinates relative to centre.
862 bool
863 FGPanelInstrument::doMouseAction (int button, int updown, int x, int y)
864 {
865   if (test()) {
866     action_list_type::iterator it = _actions.begin();
867     action_list_type::iterator last = _actions.end();
868     for ( ; it != last; it++) {
869       if ((*it)->inArea(button, x, y) &&
870           (*it)->doAction(updown))
871         return true;
872     }
873   }
874   return false;
875 }
876
877
878 \f
879 ////////////////////////////////////////////////////////////////////////
880 // Implementation of FGLayeredInstrument.
881 ////////////////////////////////////////////////////////////////////////
882
883 FGLayeredInstrument::FGLayeredInstrument (int x, int y, int w, int h)
884   : FGPanelInstrument(x, y, w, h)
885 {
886 }
887
888 FGLayeredInstrument::~FGLayeredInstrument ()
889 {
890   for (layer_list::iterator it = _layers.begin(); it != _layers.end(); it++) {
891     delete *it;
892     *it = 0;
893   }
894 }
895
896 void
897 FGLayeredInstrument::draw (osg::State& state)
898 {
899   if (!test())
900     return;
901   
902   for (int i = 0; i < (int)_layers.size(); i++) {
903     glPushMatrix();
904     _layers[i]->draw(state);
905     glPopMatrix();
906   }
907 }
908
909 int
910 FGLayeredInstrument::addLayer (FGInstrumentLayer *layer)
911 {
912   int n = _layers.size();
913   if (layer->getWidth() == -1) {
914     layer->setWidth(getWidth());
915   }
916   if (layer->getHeight() == -1) {
917     layer->setHeight(getHeight());
918   }
919   _layers.push_back(layer);
920   return n;
921 }
922
923 int
924 FGLayeredInstrument::addLayer (const FGCroppedTexture &texture,
925                                int w, int h)
926 {
927   return addLayer(new FGTexturedLayer(texture, w, h));
928 }
929
930 void
931 FGLayeredInstrument::addTransformation (FGPanelTransformation * transformation)
932 {
933   int layer = _layers.size() - 1;
934   _layers[layer]->addTransformation(transformation);
935 }
936
937
938 \f
939 ////////////////////////////////////////////////////////////////////////
940 // Implementation of FGSpecialInstrument.
941 ////////////////////////////////////////////////////////////////////////
942
943 FGSpecialInstrument::FGSpecialInstrument (DCLGPS* sb)
944   : FGPanelInstrument()
945 {
946   complex = sb;
947 }
948
949 FGSpecialInstrument::~FGSpecialInstrument ()
950 {
951 }
952
953 void
954 FGSpecialInstrument::draw (osg::State& state)
955 {
956   complex->draw(state);
957 }
958
959
960 \f
961 ////////////////////////////////////////////////////////////////////////
962 // Implementation of FGInstrumentLayer.
963 ////////////////////////////////////////////////////////////////////////
964
965 FGInstrumentLayer::FGInstrumentLayer (int w, int h)
966   : _w(w),
967     _h(h)
968 {
969 }
970
971 FGInstrumentLayer::~FGInstrumentLayer ()
972 {
973   for (transformation_list::iterator it = _transformations.begin();
974        it != _transformations.end();
975        it++) {
976     delete *it;
977     *it = 0;
978   }
979 }
980
981 void
982 FGInstrumentLayer::transform () const
983 {
984   transformation_list::const_iterator it = _transformations.begin();
985   transformation_list::const_iterator last = _transformations.end();
986   while (it != last) {
987     FGPanelTransformation *t = *it;
988     if (t->test()) {
989       float val = (t->node == 0 ? 0.0 : t->node->getFloatValue());
990
991       if (t->has_mod)
992           val = fmod(val, t->mod);
993       if (val < t->min) {
994         val = t->min;
995       } else if (val > t->max) {
996         val = t->max;
997       }
998
999       if (t->table==0) {
1000         val = val * t->factor + t->offset;
1001       } else {
1002         val = t->table->interpolate(val) * t->factor + t->offset;
1003       }
1004       
1005       switch (t->type) {
1006       case FGPanelTransformation::XSHIFT:
1007         glTranslatef(val, 0.0, 0.0);
1008         break;
1009       case FGPanelTransformation::YSHIFT:
1010         glTranslatef(0.0, val, 0.0);
1011         break;
1012       case FGPanelTransformation::ROTATION:
1013         glRotatef(-val, 0.0, 0.0, 1.0);
1014         break;
1015       }
1016     }
1017     it++;
1018   }
1019 }
1020
1021 void
1022 FGInstrumentLayer::addTransformation (FGPanelTransformation * transformation)
1023 {
1024   _transformations.push_back(transformation);
1025 }
1026
1027
1028 \f
1029 ////////////////////////////////////////////////////////////////////////
1030 // Implementation of FGGroupLayer.
1031 ////////////////////////////////////////////////////////////////////////
1032
1033 FGGroupLayer::FGGroupLayer ()
1034 {
1035 }
1036
1037 FGGroupLayer::~FGGroupLayer ()
1038 {
1039   for (unsigned int i = 0; i < _layers.size(); i++)
1040     delete _layers[i];
1041 }
1042
1043 void
1044 FGGroupLayer::draw (osg::State& state)
1045 {
1046   if (test()) {
1047     transform();
1048     int nLayers = _layers.size();
1049     for (int i = 0; i < nLayers; i++)
1050       _layers[i]->draw(state);
1051   }
1052 }
1053
1054 void
1055 FGGroupLayer::addLayer (FGInstrumentLayer * layer)
1056 {
1057   _layers.push_back(layer);
1058 }
1059
1060
1061 \f
1062 ////////////////////////////////////////////////////////////////////////
1063 // Implementation of FGTexturedLayer.
1064 ////////////////////////////////////////////////////////////////////////
1065
1066
1067 FGTexturedLayer::FGTexturedLayer (const FGCroppedTexture &texture, int w, int h)
1068   : FGInstrumentLayer(w, h),
1069     _emissive(false)
1070 {
1071   setTexture(texture);
1072 }
1073
1074
1075 FGTexturedLayer::~FGTexturedLayer ()
1076 {
1077 }
1078
1079
1080 void
1081 FGTexturedLayer::draw (osg::State& state)
1082 {
1083   if (test()) {
1084     int w2 = _w / 2;
1085     int h2 = _h / 2;
1086     
1087     transform();
1088     state.pushStateSet(_texture.getTexture());
1089     state.apply();
1090     state.setActiveTextureUnit(0);
1091     state.setClientActiveTextureUnit(0);
1092
1093     glBegin(GL_POLYGON);
1094
1095     if (_emissive) {
1096       glColor4fv( emissive_panel_color );
1097     } else {
1098                                 // From Curt: turn on the panel
1099                                 // lights after sundown.
1100       glColor4fv( panel_color );
1101     }
1102
1103     glTexCoord2f(_texture.getMinX(), _texture.getMinY()); glVertex2f(-w2, -h2);
1104     glTexCoord2f(_texture.getMaxX(), _texture.getMinY()); glVertex2f(w2, -h2);
1105     glTexCoord2f(_texture.getMaxX(), _texture.getMaxY()); glVertex2f(w2, h2);
1106     glTexCoord2f(_texture.getMinX(), _texture.getMaxY()); glVertex2f(-w2, h2);
1107     glEnd();
1108     state.popStateSet();
1109     state.apply();
1110     state.setActiveTextureUnit(0);
1111     state.setClientActiveTextureUnit(0);
1112
1113   }
1114 }
1115
1116
1117 \f
1118 ////////////////////////////////////////////////////////////////////////
1119 // Implementation of FGTextLayer.
1120 ////////////////////////////////////////////////////////////////////////
1121
1122 FGTextLayer::FGTextLayer (int w, int h)
1123   : FGInstrumentLayer(w, h), _pointSize(14.0), _font_name("Helvetica.txf")
1124 {
1125   _then.stamp();
1126   _color[0] = _color[1] = _color[2] = 0.0;
1127   _color[3] = 1.0;
1128 }
1129
1130 FGTextLayer::~FGTextLayer ()
1131 {
1132   chunk_list::iterator it = _chunks.begin();
1133   chunk_list::iterator last = _chunks.end();
1134   for ( ; it != last; it++) {
1135     delete *it;
1136   }
1137 }
1138
1139 void
1140 FGTextLayer::draw (osg::State& state)
1141 {
1142   if (test()) {
1143     glColor4fv(_color);
1144     transform();
1145
1146     FGFontCache *fc = globals->get_fontcache();
1147     fntFont* font = fc->getTexFont(_font_name.c_str());
1148     if (!font) {
1149         return; // don't crash on missing fonts
1150     }
1151     
1152     text_renderer.setFont(font);
1153
1154     text_renderer.setPointSize(_pointSize);
1155     text_renderer.begin();
1156     text_renderer.start3f(0, 0, 0);
1157
1158     _now.stamp();
1159     double diff = (_now - _then).toUSecs();
1160
1161     if (diff > 100000 || diff < 0 ) {
1162       // ( diff < 0 ) is a sanity check and indicates our time stamp
1163       // difference math probably overflowed.  We can handle a max
1164       // difference of 35.8 minutes since the returned value is in
1165       // usec.  So if the panel is left off longer than that we can
1166       // over flow the math with it is turned back on.  This (diff <
1167       // 0) catches that situation, get's us out of trouble, and
1168       // back on track.
1169       recalc_value();
1170       _then = _now;
1171     }
1172
1173     // Something is goofy.  The code in this file renders only CCW
1174     // polygons, and I have verified that the font code in plib
1175     // renders only CCW trianbles.  Yet they come out backwards.
1176     // Something around here or in plib is either changing the winding
1177     // order or (more likely) pushing a left-handed matrix onto the
1178     // stack.  But I can't find it; get out the chainsaw...
1179     glFrontFace(GL_CW);
1180     text_renderer.puts((char *)(_value.c_str()));
1181     glFrontFace(GL_CCW);
1182
1183     text_renderer.end();
1184     glColor4f(1.0, 1.0, 1.0, 1.0);      // FIXME
1185   }
1186 }
1187
1188 void
1189 FGTextLayer::addChunk (FGTextLayer::Chunk * chunk)
1190 {
1191   _chunks.push_back(chunk);
1192 }
1193
1194 void
1195 FGTextLayer::setColor (float r, float g, float b)
1196 {
1197   _color[0] = r;
1198   _color[1] = g;
1199   _color[2] = b;
1200   _color[3] = 1.0;
1201 }
1202
1203 void
1204 FGTextLayer::setPointSize (float size)
1205 {
1206   _pointSize = size;
1207 }
1208
1209 void
1210 FGTextLayer::setFontName(const string &name)
1211 {
1212   _font_name = name + ".txf";
1213   FGFontCache *fc = globals->get_fontcache();
1214   fntFont* font = fc->getTexFont(_font_name.c_str());
1215   if (!font) {
1216       SG_LOG(SG_COCKPIT, SG_WARN, "unable to find font:" << name);
1217   }
1218 }
1219
1220
1221 void
1222 FGTextLayer::setFont(fntFont * font)
1223 {
1224   text_renderer.setFont(font);
1225 }
1226
1227
1228 void
1229 FGTextLayer::recalc_value () const
1230 {
1231   _value = "";
1232   chunk_list::const_iterator it = _chunks.begin();
1233   chunk_list::const_iterator last = _chunks.end();
1234   for ( ; it != last; it++) {
1235     _value += (*it)->getValue();
1236   }
1237 }
1238
1239
1240 \f
1241 ////////////////////////////////////////////////////////////////////////
1242 // Implementation of FGTextLayer::Chunk.
1243 ////////////////////////////////////////////////////////////////////////
1244
1245 FGTextLayer::Chunk::Chunk (const string &text, const string &fmt)
1246   : _type(FGTextLayer::TEXT), _fmt(fmt)
1247 {
1248   _text = text;
1249   if (_fmt.empty()) 
1250     _fmt = "%s";
1251 }
1252
1253 FGTextLayer::Chunk::Chunk (ChunkType type, const SGPropertyNode * node,
1254                            const string &fmt, float mult, float offs,
1255                            bool truncation)
1256   : _type(type), _fmt(fmt), _mult(mult), _offs(offs), _trunc(truncation)
1257 {
1258   if (_fmt.empty()) {
1259     if (type == TEXT_VALUE)
1260       _fmt = "%s";
1261     else
1262       _fmt = "%.2f";
1263   }
1264   _node = node;
1265 }
1266
1267 const char *
1268 FGTextLayer::Chunk::getValue () const
1269 {
1270   if (test()) {
1271     _buf[0] = '\0';
1272     switch (_type) {
1273     case TEXT:
1274       sprintf(_buf, _fmt.c_str(), _text.c_str());
1275       return _buf;
1276     case TEXT_VALUE:
1277       sprintf(_buf, _fmt.c_str(), _node->getStringValue());
1278       break;
1279     case DOUBLE_VALUE:
1280       double d = _offs + _node->getFloatValue() * _mult;
1281       if (_trunc)  d = (d < 0) ? -floor(-d) : floor(d);
1282       sprintf(_buf, _fmt.c_str(), d);
1283       break;
1284     }
1285     return _buf;
1286   } else {
1287     return "";
1288   }
1289 }
1290
1291
1292 \f
1293 ////////////////////////////////////////////////////////////////////////
1294 // Implementation of FGSwitchLayer.
1295 ////////////////////////////////////////////////////////////////////////
1296
1297 FGSwitchLayer::FGSwitchLayer ()
1298   : FGGroupLayer()
1299 {
1300 }
1301
1302 void
1303 FGSwitchLayer::draw (osg::State& state)
1304 {
1305   if (test()) {
1306     transform();
1307     int nLayers = _layers.size();
1308     for (int i = 0; i < nLayers; i++) {
1309       if (_layers[i]->test()) {
1310           _layers[i]->draw(state);
1311           return;
1312       }
1313     }
1314   }
1315 }
1316
1317 \f
1318 // end of panel.cxx