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