]> git.mxchange.org Git - flightgear.git/commitdiff
3D panel support from Andy Ross:
authordavid <david>
Fri, 28 Jun 2002 14:17:40 +0000 (14:17 +0000)
committerdavid <david>
Fri, 28 Jun 2002 14:17:40 +0000 (14:17 +0000)
+ The panel(s) are now an first-class SSG node inside the aircraft
  scene graph.  There's a little code added to model.cxx to handle the
  parsing, but most of the changes are inside the new FGPanelNode
  class (Model/panelnode.[ch]xx).

+ The old FGPanel source changed a lot, but mostly cosmetically.  The
  virtual-cockpit code moved out into FGPanelNode, and the core
  rendering has been abstracted into a draw() method that doesn't try
  to set any OpenGL state.  I also replaced the old inter-layer offset
  code with glPolygonOffset, as calculating the right Z values is hard
  across the funky modelview matrix I need to use.  The older virtual
  panel code got away with it by disabling depth test, thus the "panel
  draws on top of yoke" bug.  PolygonOffset is really the appropriate
  solution for this sort of task anyway.

+ The /sim/virtual-cockpit property is no more.  The 2D panels are
  still specified in the -set.xml file, but 3D panels are part of the
  model file.

+ You can have as many 3D panels as you like.

Problems:

+ The mouse support isn't ready yet, so the 3D panels still aren't
  interactive.  Soon to come.

+ Being part of the same scene graph as the model, the 3D panels now
  "jitter" in exactly the same way.  While this makes the jitter of
  the attitude gyro less noticeable, it's still *very* noticeable and
  annoying.  I looked hard for this, and am at this point convinced
  that the problem is with the two orientation computations.  We have
  one in FGLocation that is used by the model code, and one in
  FGViewer that is used at the top of the scene graph.  My suspicion
  is that they don't agree exactly, so the final orientation matrix is
  the right answer plus the difference.  I did rule out the FDMs
  though.  None of them show more than about 0.0001 degree of
  orientation change between frames for a stopped aircraft.  That's
  within an order of magnitude of what you'd expect for the
  orientation change due to the rotation of the earth (which we don't
  model -- I cite it only as evidence of how small this is); far, far
  less than one pixel on the screen.

[and later]

OK, this is fixed by the attached panel.cxx file.  What's happened is
that the winding order for the text layer's polygons is wrong, so I
reverse it before drawing.  That's largely a hatchet job to make
things work for now, though.  We should figure out why the winding
order is wrong for only text layers and fix it.  I checked the plib
sources -- they're definitely doing things CCW, as is all the rest of
the panel code.

Odd.  I'm also not sure why the 2D panel doesn't care (it works in
both winding orders).  But this will allow you to check in working
code, anyway.  There's a big comment to this effect in there.

src/Cockpit/panel.cxx
src/Cockpit/panel.hxx
src/Model/Makefile.am
src/Model/model.cxx
src/Model/panelnode.cxx [new file with mode: 0644]
src/Model/panelnode.hxx [new file with mode: 0644]

index bee42a477766ecd75ba3cf9e3eaccf26fa62c670..a7cbecb1a4192126b0a32205a3165fccc331feb0 100644 (file)
 #define WIN_W 1024
 #define WIN_H 768
 
+// The number of polygon-offset "units" to place between layers.  In
+// principle, one is supposed to be enough.  In practice, I find that
+// my hardware/driver requires many more.
+#define POFF_UNITS 40
+
 #if defined( NONE ) && defined( _MSC_VER )
 #  pragma message( "A sloppy coder has defined NONE as a macro!!!" )
 #  undef NONE
@@ -89,8 +94,7 @@ fgPanelVisible ()
        return false;
      if(globals->get_viewmgr()->get_current() != 0)
        return false;
-     if(globals->get_current_view()->getHeadingOffset_deg() * SGD_DEGREES_TO_RADIANS != 0 &&
-        !fgGetBool("/sim/virtual-cockpit"))
+     if(globals->get_current_view()->getHeadingOffset_deg() * SGD_DEGREES_TO_RADIANS != 0)
        return false;
      return true;
 }
@@ -312,6 +316,7 @@ FGPanel::update (GLfloat winx, GLfloat winw, GLfloat winy, GLfloat winh)
   double x_offset = _x_offset;
   double y_offset = _y_offset;
 
+#if 0
   if (_jitter != 0.0) {
     double a_x_pilot = current_aircraft.fdm_state->get_A_X_pilot();
     double a_y_pilot = current_aircraft.fdm_state->get_A_Y_pilot();
@@ -328,23 +333,40 @@ FGPanel::update (GLfloat winx, GLfloat winw, GLfloat winy, GLfloat winh)
     x_offset += x_adjust;
     y_offset += y_adjust;
   }
+#endif
 
-  if(fgGetBool("/sim/virtual-cockpit")) {
-      setupVirtualCockpit();
-  } else {
-      glMatrixMode(GL_PROJECTION);
-      glPushMatrix();
-      glLoadIdentity();
-      gluOrtho2D(winx, winx + winw, winy, winy + winh); /* right side up */
-      // gluOrtho2D(winx + winw, winx, winy + winh, winy); /* up side down */
-      
-      glMatrixMode(GL_MODELVIEW);
-      glPushMatrix();
-      glLoadIdentity();
-      
-      glTranslated(x_offset, y_offset, 0);
-  }
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  glLoadIdentity();
+  gluOrtho2D(winx, winx + winw, winy, winy + winh); /* right side up */
+  // gluOrtho2D(winx + winw, winx, winy + winh, winy); /* up side down */
   
+  glMatrixMode(GL_MODELVIEW);
+  glPushMatrix();
+  glLoadIdentity();
+  
+  glTranslated(x_offset, y_offset, 0);
+  
+  draw();
+
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glPopMatrix();
+
+  ssgForceBasicState();
+  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+}
+
+void
+FGPanel::draw()
+{
+  // In 3D mode, it's possible that we are being drawn exactly on top
+  // of an existing polygon.  Use an offset to prevent z-fighting.  In
+  // 2D mode, this is a no-op.
+  glEnable(GL_POLYGON_OFFSET_FILL);
+  glPolygonOffset(0, -POFF_UNITS);
+
   // Draw the background
   glEnable(GL_TEXTURE_2D);
   glDisable(GL_LIGHTING);
@@ -363,10 +385,10 @@ FGPanel::update (GLfloat winx, GLfloat winw, GLfloat winy, GLfloat winh)
     // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
     glBegin(GL_POLYGON);
-    glTexCoord2f(0.0, 0.0); glVertex3f(WIN_X, WIN_Y, 0);
-    glTexCoord2f(1.0, 0.0); glVertex3f(WIN_X + _width, WIN_Y, 0);
-    glTexCoord2f(1.0, 1.0); glVertex3f(WIN_X + _width, WIN_Y + _height, 0);
-    glTexCoord2f(0.0, 1.0); glVertex3f(WIN_X, WIN_Y + _height, 0);
+    glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X, WIN_Y);
+    glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + _width, WIN_Y);
+    glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + _width, WIN_Y + _height);
+    glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X, WIN_Y + _height);
     glEnd();
   } else {
     for (int i = 0; i < 4; i ++) {
@@ -374,25 +396,24 @@ FGPanel::update (GLfloat winx, GLfloat winw, GLfloat winy, GLfloat winh)
       glBindTexture(GL_TEXTURE_2D, _mbg[i*2]->getHandle());
       glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
       glBegin(GL_POLYGON);
-      glTexCoord2f(0.0, 0.0); glVertex3f(WIN_X + (_width/4) * i, WIN_Y + (_height/2), 0);
-      glTexCoord2f(1.0, 0.0); glVertex3f(WIN_X + (_width/4) * (i+1), WIN_Y + (_height/2), 0);
-      glTexCoord2f(1.0, 1.0); glVertex3f(WIN_X + (_width/4) * (i+1), WIN_Y + _height, 0);
-      glTexCoord2f(0.0, 1.0); glVertex3f(WIN_X + (_width/4) * i, WIN_Y + _height, 0);
+      glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + (_height/2));
+      glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + (_height/2));
+      glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + _height);
+      glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + _height);
       glEnd();
       // bottom row of textures...(2,4,6,8)
       glBindTexture(GL_TEXTURE_2D, _mbg[(i*2)+1]->getHandle());
       glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
       glBegin(GL_POLYGON);
-      glTexCoord2f(0.0, 0.0); glVertex3f(WIN_X + (_width/4) * i, WIN_Y, 0);
-      glTexCoord2f(1.0, 0.0); glVertex3f(WIN_X + (_width/4) * (i+1), WIN_Y, 0);
-      glTexCoord2f(1.0, 1.0); glVertex3f(WIN_X + (_width/4) * (i+1), WIN_Y + (_height/2), 0);
-      glTexCoord2f(0.0, 1.0); glVertex3f(WIN_X + (_width/4) * i, WIN_Y + (_height/2), 0);
+      glTexCoord2f(0.0, 0.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y);
+      glTexCoord2f(1.0, 0.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y);
+      glTexCoord2f(1.0, 1.0); glVertex2f(WIN_X + (_width/4) * (i+1), WIN_Y + (_height/2));
+      glTexCoord2f(0.0, 1.0); glVertex2f(WIN_X + (_width/4) * i, WIN_Y + (_height/2));
       glEnd();
     }
-
   }
 
-                               // Draw the instruments.
+  // Draw the instruments.
   instrument_list_type::const_iterator current = _instruments.begin();
   instrument_list_type::const_iterator end = _instruments.end();
 
@@ -404,107 +425,9 @@ FGPanel::update (GLfloat winx, GLfloat winw, GLfloat winy, GLfloat winh)
     glPopMatrix();
   }
 
-  if(fgGetBool("/sim/virtual-cockpit")) {
-      cleanupVirtualCockpit();
-  } else {
-      glMatrixMode(GL_PROJECTION);
-      glPopMatrix();
-      glMatrixMode(GL_MODELVIEW);
-      glPopMatrix();
-  }
-
-  ssgForceBasicState();
-  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-}
-
-void
-FGPanel::setupVirtualCockpit()
-{
-    int i;
-    FGViewer* view = globals->get_current_view();
-
-    // Generate corners for the panel quad.  Put the top edge of the
-    // panel 1m in and 6 degrees down from the forward direction, and
-    // make the whole thing 60 degrees wide.  In principle, these
-    // should be settable per-panel, so that you can have lots of
-    // panel objects plastered about the cockpit in realistic
-    // positions and orientations.
-    float a[3], b[3], c[3];
-    float pw = tan(30*SGD_DEGREES_TO_RADIANS);
-    float ph = 2 * pw * (float)_height/(float)_width;
-    float ptop = -tan(6*SGD_DEGREES_TO_RADIANS);
-    a[0] = -pw; a[1] = ptop-ph; a[2] = -1; // bottom left
-    b[0] =  pw; b[1] = ptop-ph; b[2] = -1; // bottom right
-    c[0] = -pw; c[1] = ptop;    c[2] = -1; // top left
-
-    // A standard projection, in meters, with especially close clip
-    // planes.
-    glMatrixMode(GL_PROJECTION);
-    glPushMatrix();
-    glLoadIdentity();
-    gluPerspective(view->get_v_fov(), 1/view->get_aspect_ratio(), 
-                  0.01, 100);
-
-    glMatrixMode(GL_MODELVIEW);
-    glPushMatrix();
-    glLoadIdentity();
-
-    // Generate a "look at" matrix using OpenGL (!) coordinate
-    // conventions.
-    float lookat[3];
-    float pitch = view->getPitchOffset_deg() * SGD_DEGREES_TO_RADIANS;
-    float rot = view->getHeadingOffset_deg() * SGD_DEGREES_TO_RADIANS;
-    lookat[0] = -sin(rot);
-    lookat[1] = sin(pitch) / cos(pitch);
-    lookat[2] = -cos(rot);
-    if(fabs(lookat[1]) > 9999) lookat[1] = 9999; // FPU sanity
-    gluLookAt(0, 0, 0, lookat[0], lookat[1], lookat[2], 0, 1, 0);
-
-    // Translate the origin to the location of the panel quad
-    glTranslatef(a[0], a[1], a[2]);
-    // Generate a matrix to translate unit square coordinates from the
-    // panel to real world coordinates.  Use a transposed basis for
-    // the panel quad.  Note: this matrix is relatively expensive to
-    // compute, and is invariant.  Consider precomputing and storing
-    // it.  Also, consider using the plib vector math routines, so the
-    // reuse junkies don't yell at me.  (Fine, I hard-coded a cross
-    // product.  Just shoot me and be done with it.)
-    float u[3], v[3], w[3], m[16];
-    for(i=0; i<3; i++) u[i] = b[i] - a[i]; // U = B - A
-    for(i=0; i<3; i++) v[i] = c[i] - a[i]; // V = C - A
-    w[0] = u[1]*v[2] - v[1]*u[2];          // W = U x V
-    w[1] = u[2]*v[0] - v[2]*u[0];
-    w[2] = u[0]*v[1] - v[0]*u[1];
-
-    m[0] = u[0]; m[4] = v[0]; m[8]  = w[0]; m[12] = 0; //     |Ux Vx Wx|
-    m[1] = u[1]; m[5] = v[1]; m[9]  = w[1]; m[13] = 0; // m = |Uy Vy Wy|
-    m[2] = u[2]; m[6] = v[2]; m[10] = w[2]; m[14] = 0; //     |Uz Vz Wz|
-    m[3] = 0;    m[7] = 0;    m[11] = 0;    m[15] = 1;
-    glMultMatrixf(m);
-
-    // Finally, a scaling factor to map the panel's width and height
-    // to the unit square.
-    glScalef(1./_width, 1./_height, 1);
-
-    // Now, turn off the Z buffer.  The panel code doesn't need
-    // it, and we're using different clip planes anyway (meaning we
-    // can't share it without glDepthRange() hackery or much
-    // framebuffer bandwidth wasteage)
-    glDisable(GL_DEPTH_TEST);
+  glDisable(GL_POLYGON_OFFSET_FILL);
 }
 
-void
-FGPanel::cleanupVirtualCockpit()
-{
-    glMatrixMode(GL_PROJECTION);
-    glPopMatrix();
-    glMatrixMode(GL_MODELVIEW);
-    glPopMatrix();
-    glEnable(GL_DEPTH_TEST);
-}
-
-
 /**
  * Set the panel's visibility.
  */
@@ -565,7 +488,6 @@ FGPanel::setYOffset (int offset)
     _y_offset = offset;
 }
 
-
 /**
  * Perform a mouse action.
  */
@@ -785,19 +707,14 @@ FGLayeredInstrument::~FGLayeredInstrument ()
 void
 FGLayeredInstrument::draw ()
 {
-  if (test()) {
-    float z = 0.1f;
-    float z_inc = 0.01;
-    bool vc = fgGetBool("/sim/virtual-cockpit");
-    for (int i = 0; i < (int)_layers.size(); i++) {
-      glPushMatrix();
-      if(!vc) {
-        glTranslatef(0.0, 0.0, z);
-        z += z_inc;
-      }
-      _layers[i]->draw();
-      glPopMatrix();
-    }
+  if (!test())
+    return;
+  
+  for (int i = 0; i < (int)_layers.size(); i++) {
+    glPushMatrix();
+    glPolygonOffset(-1, -POFF_UNITS*(i+2));
+    _layers[i]->draw();
+    glPopMatrix();
   }
 }
 
@@ -999,7 +916,6 @@ void
 FGTextLayer::draw ()
 {
   if (test()) {
-    glPushMatrix();
     glColor4fv(_color);
     transform();
     if ( _font_name == "led" ) {
@@ -1013,20 +929,7 @@ FGTextLayer::draw ()
 
     _now.stamp();
     long diff = _now - _then;
-#if 0
-    // It would be nice to keep this #ifdef'd stuff for (04/18/2002 +
-    // a couple days) as I verify my solution to the panel text
-    // drawing problem is actually correct. -CLO
-    cout << "time diff = " << diff << endl;
-    if ( _now - _then < 0 ) {
-        cout << "Eeek, the past is now in the future!" << endl;
-        cout << "Now = " << _now.get_seconds() << " seconds "
-             << _now.get_usec() << "usecs" << endl;
-        cout << "Past = " << _then.get_seconds() << " seconds "
-             << _then.get_usec() << "usecs" << endl;
-        exit(-1);
-    }
-#endif
+
     if (diff > 100000 || diff < 0 ) {
       // ( diff < 0 ) is a sanity check and indicates our time stamp
       // difference math probably overflowed.  We can handle a max
@@ -1038,11 +941,19 @@ FGTextLayer::draw ()
       recalc_value();
       _then = _now;
     }
+
+    // Something is goofy.  The code in this file renders only CCW
+    // polygons, and I have verified that the font code in plib
+    // renders only CCW trianbles.  Yet they come out backwards.
+    // Something around here or in plib is either changing the winding
+    // order or (more likely) pushing a left-handed matrix onto the
+    // stack.  But I can't find it; get out the chainsaw...
+    glFrontFace(GL_CW);
     text_renderer.puts((char *)(_value.c_str()));
+    glFrontFace(GL_CCW);
 
     text_renderer.end();
     glColor4f(1.0, 1.0, 1.0, 1.0);     // FIXME
-    glPopMatrix();
   }
 }
 
index c17b897357dc0ba5c1d97d8f4be0e9c03b96ca1a..b038b828d566dfe893506f58bdc2ae5620e1320d 100644 (file)
@@ -141,6 +141,7 @@ public:
   virtual void init ();
   virtual void bind ();
   virtual void unbind ();
+  virtual void draw ();
   virtual void update (double dt);
   virtual void update (GLfloat winx, GLfloat winw, GLfloat winy, GLfloat winh);
 
index 7cc804d046e58300a8ffa189163667c5ef5d0d4d..e932518fef823e0811dbeed53db5ad0fc87d08b6 100644 (file)
@@ -2,7 +2,8 @@ noinst_LIBRARIES = libModel.a
 
 libModel_a_SOURCES = model.cxx model.hxx \
                      modelmgr.cxx modelmgr.hxx \
-                     acmodel.cxx acmodel.hxx
+                     acmodel.cxx acmodel.hxx \
+                     panelnode.cxx panelnode.hxx
 
 if OLD_AUTOMAKE
 INCLUDES += -I$(top_srcdir) -I$(top_srcdir)/src
index a61b23199ec300ea5917ab5a4bdfea8a07347f83..64621ea0c67429af8df04b4b0b33b99a99a261db 100644 (file)
@@ -26,6 +26,7 @@
 #include <Scenery/scenery.hxx>
 
 #include "model.hxx"
+#include "panelnode.hxx"
 
 
 \f
@@ -244,6 +245,14 @@ FG3DModel::init (const string &path)
     }
   }
 
+                                // Load panels
+  vector<SGPropertyNode_ptr> panel_nodes = props.getChildren("panel");
+  for (i = 0; i < panel_nodes.size(); i++) {
+    printf("Reading a panel in model.cxx\n");
+    FGPanelNode * panel = new FGPanelNode(panel_nodes[i]);
+    _model->addKid(panel);
+  }
+
                                // Load sub-models
   vector<SGPropertyNode_ptr> model_nodes = props.getChildren("model");
   for (i = 0; i < model_nodes.size(); i++) {
diff --git a/src/Model/panelnode.cxx b/src/Model/panelnode.cxx
new file mode 100644 (file)
index 0000000..e49f319
--- /dev/null
@@ -0,0 +1,100 @@
+#include <GL/gl.h>
+#include <Main/fg_props.hxx>
+#include <Cockpit/panel.hxx>
+#include <Cockpit/panel_io.hxx>
+#include "panelnode.hxx"
+
+FGPanelNode::FGPanelNode(SGPropertyNode* props)
+{
+    // Make an FGPanel object.  But *don't* call init() or bind() on
+    // it -- those methods touch static state.
+    _panel = fgReadPanel(props->getStringValue("path"));
+
+    // Read out the pixel-space info
+    _xmax = _panel->getWidth();
+    _ymax = _panel->getHeight();
+
+    // And the corner points
+    SGPropertyNode* pt = props->getChild("bottom-left");
+    _bottomLeft[0] = pt->getFloatValue("x-m");
+    _bottomLeft[1] = pt->getFloatValue("y-m");
+    _bottomLeft[2] = pt->getFloatValue("z-m");
+
+    pt = props->getChild("top-left");
+    _topLeft[0] = pt->getFloatValue("x-m");
+    _topLeft[1] = pt->getFloatValue("y-m");
+    _topLeft[2] = pt->getFloatValue("z-m");
+
+    pt = props->getChild("bottom-right");
+    _bottomRight[0] = pt->getFloatValue("x-m");
+    _bottomRight[1] = pt->getFloatValue("y-m");
+    _bottomRight[2] = pt->getFloatValue("z-m");
+
+    // Now generate out transformation matrix.  For shorthand, use
+    // "a", "b", and "c" as our corners and "m" as the matrix. The
+    // vector u goes from a to b, v from a to c, and w is a
+    // perpendicular cross product.
+    float *a = _bottomLeft, *b = _bottomRight, *c = _topLeft, *m = _xform;
+    float u[3], v[3], w[3];
+    int i;
+    for(i=0; i<3; i++) u[i] = b[i] - a[i]; // U = B - A
+    for(i=0; i<3; i++) v[i] = c[i] - a[i]; // V = C - A
+
+    w[0] = u[1]*v[2] - v[1]*u[2];          // W = U x V
+    w[1] = u[2]*v[0] - v[2]*u[0];
+    w[2] = u[0]*v[1] - v[0]*u[1];
+
+    // Now generate a trivial basis transformation matrix.  If we want
+    // to map the three unit vectors to three arbitrary vectors U, V,
+    // and W, then those just become the columns of the 3x3 matrix.
+    m[0] = u[0]; m[4] = v[0]; m[8]  = w[0]; m[12] = a[0]; //     |Ux Vx Wx|
+    m[1] = u[1]; m[5] = v[1]; m[9]  = w[1]; m[13] = a[1]; // m = |Uy Vy Wy|
+    m[2] = u[2]; m[6] = v[2]; m[10] = w[2]; m[14] = a[2]; //     |Uz Vz Wz|
+    m[3] = 0;    m[7] = 0;    m[11] = 0;    m[15] = 1;
+
+    // The above matrix maps the unit (!) square to the panel
+    // rectangle.  Postmultiply scaling factors that match the
+    // pixel-space size of the panel.
+    for(i=0; i<4; i++) {
+        m[0+i] *= 1.0/_xmax;
+        m[4+i] *= 1.0/_ymax;
+    }
+
+    // Now plib initialization.  The bounding sphere is defined nicely
+    // by our corner points:
+    float cx = (b[0]+c[0])/2;
+    float cy = (b[1]+c[1])/2;
+    float cz = (b[2]+c[2])/2;
+    float r = sqrt((cx-a[0])*(cx-a[0]) +
+                   (cy-a[1])*(cy-a[1]) +
+                   (cz-a[2])*(cz-a[2]));
+    bsphere.setCenter(cx, cy, cz);
+    bsphere.setRadius(r);
+}
+
+FGPanelNode::~FGPanelNode()
+{
+    delete _panel;
+}
+
+void FGPanelNode::draw()
+{
+    // What's the difference?
+    draw_geometry();
+}
+
+void FGPanelNode::draw_geometry()
+{
+    glMatrixMode(GL_MODELVIEW);
+    glPushMatrix();
+    glMultMatrixf(_xform);
+    _panel->draw();
+    glPopMatrix();
+}
+
+void FGPanelNode::die()
+{
+    SG_LOG(SG_ALL,SG_ALERT,"Unimplemented function called on FGPanelNode");
+    *(int*)0=0;
+}
+
diff --git a/src/Model/panelnode.hxx b/src/Model/panelnode.hxx
new file mode 100644 (file)
index 0000000..f3c75da
--- /dev/null
@@ -0,0 +1,70 @@
+#include <plib/ssg.h>
+
+class FGPanel;
+class SGPropertyNode;
+
+// PanelNode defines an SSG leaf object that draws a FGPanel object
+// into the scene graph.  Note that this is an incomplete SSG object,
+// many methods, mostly involved with modelling and runtime
+// inspection, are unimplemented.
+
+class FGPanelNode : public ssgLeaf 
+{
+protected:
+    virtual void draw_geometry();
+
+public:
+    FGPanelNode(SGPropertyNode* props);
+    virtual ~FGPanelNode();
+
+    virtual void draw();
+    void mouseEvent(int button, int updown, int x, int y);
+
+    virtual void recalcBSphere() { bsphere_is_invalid = 0; }
+
+    //
+    // A bunch of Plib functions that aren't implemented.  I don't
+    // even know what many of them do, but they're pure virtual and
+    // require implementation.
+    //
+    virtual int getNumTriangles() { die(); return 0; }
+    virtual void getTriangle(int n, short* v1, short* v2, short* v3) { die(); }
+    virtual int getNumLines() { die(); return 0; }
+    virtual void getLine(int n, short* v1, short* v2) { die(); }
+    
+    virtual void drawHighlight(sgVec4 colour) { die(); }
+    virtual void drawHighlight(sgVec4 colour, int i) { die(); }
+    virtual float* getVertex(int i) { die(); return 0; }
+    virtual float* getNormal(int i) { die(); return 0; }
+    virtual float* getColour(int i) { die(); return 0; }
+    virtual float* getTexCoord(int i) { die(); return 0; }
+    virtual void pick(int baseName) { die(); }
+    virtual void isect_triangles(sgSphere* s, sgMat4 m, int testNeeded) { die(); }
+    virtual void hot_triangles(sgVec3 s, sgMat4 m, int testNeeded) { die(); }
+    virtual void los_triangles(sgVec3 s, sgMat4 m, int testNeeded) { die(); }
+    virtual void transform(const sgMat4 m) { die(); }
+
+private:
+    // Handler for all the unimplemented methods above
+    void die();
+
+    FGPanel* _panel;
+
+    // Panel corner coordinates
+    float _bottomLeft[3], _topLeft[3], _bottomRight[3];
+
+    // The input range expected in the panel definition.  These x/y
+    // coordinates will map to the right/top sides.
+    float _xmax, _ymax;
+    
+    // The matrix that results, which transforms 2D x/y panel
+    // coordinates into 3D coordinates of the panel quadrilateral.
+    GLfloat _xform[16];
+
+    // The matrix transformation state that was active the last time
+    // we were rendered.  Used by the mouse code to compute
+    // intersections.
+    GLfloat _lastModelview[16];
+    GLfloat _lastProjection[16];
+    GLint   _lastViewport[4];
+};