/***********************************************************************
 * FXRuby -- the Ruby language bindings for the FOX GUI toolkit.
 * Copyright (c) 2001-2003 by J. Lyle Johnson. All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For further information please contact the author by e-mail
 * at "lyle@users.sourceforge.net".
 ***********************************************************************/

class FXDCPrint;
class FXGLObject;


// Pick tolerance
#define PICK_TOL  3


// GL Viewer options
enum {
  VIEWER_LOCKED      = 0x00008000,    /// Mouse based view manipulation is locked
  VIEWER_LIGHTING    = 0x00010000,    /// Lighting is on
  VIEWER_FOG         = 0x00020000,    /// Fog mode on
  VIEWER_DITHER      = 0x00040000     /// Dithering
  };

  
/*******************************  Viewer  Structs  *****************************/
  

/// OpenGL Viewer Viewport
struct FXViewport {
  FXViewport();
  FXint      w,h;               // Viewport dimensions
  FXdouble   left,right;        // World box
  FXdouble   bottom,top;
  FXdouble   hither,yon;
  ~FXViewport();
  };


// OpenGL Light Source
struct FXLight {
  FXLight();
  FXHVec     ambient;           // Ambient light color
  FXHVec     diffuse;           // Diffuse light color
  FXHVec     specular;          // Specular light color
  FXHVec     position;          // Light position
  FXVec      direction;         // Spot direction
  FXfloat    exponent;          // Spotlight exponent
  FXfloat    cutoff;            // Spotlight cutoff angle
  FXfloat    c_attn;            // Constant attenuation factor
  FXfloat    l_attn;            // Linear attenuation factor
  FXfloat    q_attn;            // Quadratic attenuation factor
  ~FXLight();
  };


// OpenGL Material Description
struct FXMaterial {
  FXMaterial();
  FXHVec     ambient;           // Ambient material color
  FXHVec     diffuse;           // Diffuse material color
  FXHVec     specular;          // Specular material color
  FXHVec     emission;          // Emissive material color
  FXfloat    shininess;         // Specular shininess
  ~FXMaterial();
  };

  
// Feedback buffer sort routine
// typedef FXbool (*FXZSortFunc)(FXfloat*& buffer,FXint& used,FXint& size); FIXME

/********************************  Viewer  Class  ******************************/


/// Canvas, an area drawn by another object
class FXGLViewer : public FXGLCanvas {
protected:
  FXViewport      wvt;              // Window viewport transform
  FXHMat          transform;        // Current transformation matrix
  FXHMat          itransform;       // Inverse of current transformation matrix
  FXuint          projection;       // Projection mode
  FXQuat          rotation;         // Viewer orientation
  FXdouble        fov;              // Field of view
  FXdouble        zoom;             // Zoom factor
  FXdouble        offset;           // Offset for lines on surfaces
  FXVec           center;           // Model center
  FXVec           scale;            // Model scale
  FXdouble        worldpx;          // Pixel size in world
  FXdouble        modelpx;          // Pixel size in model
  FXuint          op;               // Operation being performed
  FXint           maxhits;          // Maximum number of hits
  FXdouble        ax,ay;            // Quick view->world coordinate mapping
  FXdouble        diameter;         // Size of model diameter ( always > 0)
  FXdouble        distance;         // Distance of PRP to target
  FXHVec          background;       // Background color
  FXHVec          ambient;          // Global ambient light
  FXLight         light;            // Light source
  FXMaterial      material;         // Base material properties
  FXint           dial[3];          // Dial positions
  FXTimer        *timer;            // Motion timer
  FXString        help;             // Status help
  FXString        tip;              // Tooltip for background
  FXbool          doesturbo;        // Doing turbo mode
  FXbool          turbomode;        // Turbo mode
  FXGLObject     *dropped;          // Object being dropped on
  FXGLObject     *selection;        // Current object
  FXZSortFunc     zsortfunc;        // Routine to sort feedback buffer
  FXGLObject     *scene;            // What we're looking at
public:

  // Common DND types
  static FXDragType objectType;     // GL Object type

protected:

  // Mouse actions when in viewing window
  enum {
    HOVERING,                       // Hovering mouse w/o doing anything
    PICKING,                        // Pick mode
    ROTATING,                       // Rotating camera around target
    POSTING,                        // Posting right-mouse menu
    TRANSLATING,                    // Translating camera
    ZOOMING,                        // Zooming
    FOVING,                         // Change field-of-view
    DRAGGING,                       // Dragging objects
    TRUCKING,                       // Trucking camera
    GYRATING,                       // Rotation of camera around eye
    DO_LASSOSELECT,                 // Lasso select when mouse pressed
    LASSOSELECT,                    // Anchor of lasso rectangle
    DO_LASSOZOOM,                   // Zoom when mouse pressed
    LASSOZOOM                       // Zoom rectangle
    };

protected:
  FXGLViewer();
  void glsetup();
  void updateProjection();
  void updateTransform();
  FXVec spherePoint(FXint px,FXint py);
  FXQuat turn(FXint fx,FXint fy,FXint tx,FXint ty);
  void drawWorld(FXViewport& wv);
  void drawAnti(FXViewport& wv);
  void drawLasso(FXint x0,FXint y0,FXint x1,FXint y1);
  // FXint selectHits(FXuint*& hits,FXint& nhits,FXint x,FXint y,FXint w,FXint h);
  FXint renderFeedback(FXfloat *buffer,FXint x,FXint y,FXint w,FXint h,FXint maxbuffer);
  void drawFeedback(FXDCPrint& pdc,const FXfloat* buffer,FXint used);
  virtual FXGLObject* processHits(FXuint *pickbuffer,FXint nhits);
  void setOp(FXuint o);
  virtual void layout();
public:

  // Events
  long onPaint(FXObject*,FXSelector,void* PTR_IGNORE);
  long onEnter(FXObject*,FXSelector,void* PTR_EVENT);
  long onLeave(FXObject*,FXSelector,void* PTR_EVENT);
  long onMotion(FXObject*,FXSelector,void* PTR_EVENT);
  long onMouseWheel(FXObject*,FXSelector,void* PTR_EVENT);
  long onChanged(FXObject*,FXSelector,void* PTR_GLOBJECT);
  long onPick(FXObject*,FXSelector,void* PTR_GLOBJECT); // FIXME
  long onClicked(FXObject*,FXSelector,void* PTR_GLOBJECT);
  long onDoubleClicked(FXObject*,FXSelector,void* PTR_GLOBJECT);
  long onTripleClicked(FXObject*,FXSelector,void* PTR_GLOBJECT);
  long onLassoed(FXObject*,FXSelector,void* PTR_EVENT);
  long onSelected(FXObject*,FXSelector,void* PTR_IGNORE); // FIXME
  long onDeselected(FXObject*,FXSelector,void* PTR_IGNORE); // FIXME
  long onInserted(FXObject*,FXSelector,void* PTR_IGNORE); // FIXME
  long onDeleted(FXObject*,FXSelector,void* PTR_IGNORE); // FIXME
  long onLeftBtnPress(FXObject*,FXSelector,void* PTR_EVENT);
  long onLeftBtnRelease(FXObject*,FXSelector,void* PTR_EVENT);
  long onMiddleBtnPress(FXObject*,FXSelector,void* PTR_EVENT);
  long onMiddleBtnRelease(FXObject*,FXSelector,void* PTR_EVENT);
  long onRightBtnPress(FXObject*,FXSelector,void* PTR_EVENT);
  long onRightBtnRelease(FXObject*,FXSelector,void* PTR_EVENT);
  long onUngrabbed(FXObject*,FXSelector,void* PTR_EVENT);
  long onKeyPress(FXObject*,FXSelector,void* PTR_EVENT);
  long onKeyRelease(FXObject*,FXSelector,void* PTR_EVENT);
  long onFocusIn(FXObject*,FXSelector,void* PTR_EVENT);
  long onFocusOut(FXObject*,FXSelector,void* PTR_EVENT);
  long onClipboardLost(FXObject*,FXSelector,void* PTR_EVENT);
  long onClipboardGained(FXObject*,FXSelector,void* PTR_EVENT);
  long onClipboardRequest(FXObject*,FXSelector,void* PTR_EVENT);

  // Commands
  long onCmdPerspective(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdPerspective(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdParallel(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdParallel(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdFront(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdFront(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdBack(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdBack(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdLeft(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdLeft(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdRight(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdRight(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdTop(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdTop(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdBottom(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdBottom(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdResetView(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdFitView(FXObject*,FXSelector,void* PTR_IGNORE);
  long onDNDEnter(FXObject*,FXSelector,void* PTR_EVENT);
  long onDNDLeave(FXObject*,FXSelector,void* PTR_EVENT);
  long onDNDMotion(FXObject*,FXSelector,void* PTR_EVENT);
  long onDNDDrop(FXObject*,FXSelector,void* PTR_EVENT);
  long onTipTimer(FXObject*,FXSelector,void* PTR_IGNORE);
  long onQueryHelp(FXObject*,FXSelector,void* PTR_IGNORE);
  long onQueryTip(FXObject*,FXSelector,void* PTR_IGNORE); // FIXME
  long onCmdXYZDial(FXObject*,FXSelector,void* PTR_INT);
  long onUpdXYZDial(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdRollPitchYaw(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdRollPitchYaw(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdXYZScale(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdXYZScale(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdCurrent(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdCutSel(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdCopySel(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdPasteSel(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdDeleteSel(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdDeleteSel(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdBackColor(FXObject*,FXSelector,void* PTR_COLOR);
  long onUpdBackColor(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdAmbientColor(FXObject*,FXSelector,void* PTR_COLOR);
  long onUpdAmbientColor(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdLock(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdLock(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdLighting(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdLighting(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdFog(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdFog(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdDither(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdDither(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdFov(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdFov(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdZoom(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdZoom(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdLightAmbient(FXObject*,FXSelector,void* PTR_COLOR);
  long onUpdLightAmbient(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdLightDiffuse(FXObject*,FXSelector,void* PTR_COLOR);
  long onUpdLightDiffuse(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdLightSpecular(FXObject*,FXSelector,void* PTR_COLOR);
  long onUpdLightSpecular(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdTurbo(FXObject*,FXSelector,void* PTR_IGNORE);
  long onUpdTurbo(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdPrintImage(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdPrintVector(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdLassoZoom(FXObject*,FXSelector,void* PTR_IGNORE);
  long onCmdLassoSelect(FXObject*,FXSelector,void* PTR_IGNORE);

public:
  
  // Projection modes
  enum {
    PARALLEL,		  // Parallel projection
    PERSPECTIVE		  // Perspective projection
    };

  // Precompiled display lists [DEPRECATED]
  enum {
    OFFSETPROJECTION  = 1,
    SURFACEPROJECTION = 2
    };
  
  // Messages
  enum {
    ID_PERSPECTIVE=FXGLCanvas::ID_LAST,
    ID_PARALLEL,
    ID_FRONT,
    ID_BACK,
    ID_LEFT,
    ID_RIGHT,
    ID_TOP,
    ID_BOTTOM,
    ID_RESETVIEW,
    ID_FITVIEW,
    ID_TIPTIMER,
    ID_BACK_COLOR,    
    ID_AMBIENT_COLOR,
    ID_LIGHT_AMBIENT,
    ID_LIGHT_DIFFUSE,
    ID_LIGHT_SPECULAR,
    ID_LIGHTING,
    ID_TURBO,
    ID_FOG,
    ID_DITHER,
    ID_SCALE_X,
    ID_SCALE_Y,
    ID_SCALE_Z,
    ID_DIAL_X,
    ID_DIAL_Y,
    ID_DIAL_Z,
    ID_ROLL,
    ID_PITCH,
    ID_YAW,
    ID_FOV,
    ID_ZOOM,
    ID_LOCK,
    ID_CUT_SEL,
    ID_COPY_SEL,
    ID_PASTE_SEL,
    ID_DELETE_SEL,
    ID_PRINT_IMAGE,
    ID_PRINT_VECTOR,
    ID_LASSO_ZOOM,
    ID_LASSO_SELECT,
    ID_LAST
    };

public:

  // Common DND type names
  %extend {
    static VALUE objectTypeName(){
      return to_ruby(FXGLViewer::objectTypeName);
      }
  }

public:
  %extend {
    /// Construct GL viewer widget
    FXGLViewer(FXComposite* p,FXGLVisual *vis,FXObject* tgt=NULL,FXSelector sel=0,FXuint opts=0,FXint x=0,FXint y=0,FXint w=0,FXint h=0){
      return new FXRbGLViewer(p,vis,tgt,sel,opts,x,y,w,h);
      }
  
    /// Construct GL viewer widget sharing display list with another GL viewer
    FXGLViewer(FXComposite* p,FXGLVisual *vis,FXGLViewer* sharegroup,FXObject* tgt=NULL,FXSelector sel=0,FXuint opts=0,FXint x=0,FXint y=0,FXint w=0,FXint h=0){
      return new FXRbGLViewer(p,vis,sharegroup,tgt,sel,opts,x,y,w,h);
      }
    }

  /// Return size of pixel in world coordinates
  FXdouble worldPix() const;

  /// Return size of pixel in model coordinates
  FXdouble modelPix() const;

#ifdef SWIGRUBY
  %extend {
    // Return a list of all objects in the given rectangle
    VALUE lasso(FXint x1,FXint y1,FXint x2,FXint y2) {
      VALUE objects = rb_ary_new();
      FXGLObject** items = self->lasso(x1, y1, x2, y2);
      if (items) {
        register FXGLObject** p = items;
        while (*p) {
          rb_ary_push(objects, to_ruby(*p));
          p++;
        }
        FXFREE(&items);
      }
      return objects;
    }
  }
#else
  %extend {
    PyObject* lasso(FXint x1,FXint y1,FXint x2,FXint y2) {
      FXGLObject **items = self->lasso(x1,y1,x2,y2);
      int size = 0;
      if (items) {
        while (items[size])
          size++;
      }

      FXbool doSave = FXPyRestoreThread();
      PyObject *obj = PyList_New(size);
      if (!obj) {
	if (items) FXFREE(&items);
        FXPySaveThread(doSave);
	return NULL;
      }

      for (FXint i=0; i<size; i++) {
	PyObject *listitem = FXPyMakeShadowObject(items[i]);
	if (!listitem || PyList_SetItem(obj, i, listitem) < 0) {
	  FXFREE(&items);
	  Py_DECREF(obj);
          FXPySaveThread(doSave);
	  return NULL;
	}
      }

      // Clean up local copy
      if (items) FXFREE(&items);
      FXPySaveThread(doSave);
      return obj;
    }

    // FXGLObject** select(FXint x,FXint y,FXint w,FXint h)
    PyObject* select(FXint x,FXint y,FXint w,FXint h) {
      FXGLObject **items = self->select(x,y,w,h);
      int size = 0;
      if (items) {
        while (items[size])
          size++;
      }
      FXbool doSave = FXPyRestoreThread();
      PyObject *obj = PyList_New(size);
      if (!obj) {
	if (items) FXFREE(&items);
        FXPySaveThread(doSave);
	return NULL;
      }

      for (FXint i=0; i<size; i++) {
	char _ptemp[128];
	SWIG_MakePtr(_ptemp,(char*)items[i],"_FXGLObject_p");
	PyObject *listitem = Py_BuildValue("s",_ptemp);
	if (!listitem || PyList_SetItem(obj,i,listitem) < 0) {
	  Py_DECREF(obj);
	  FXFREE(&items);
          FXPySaveThread(doSave);
	  return NULL;
	}
      }

      // Clean up local copy
      if (items) FXFREE(&items);
      FXPySaveThread(doSave);
      return obj;
    }
  }
#endif

  /// Change the model bounding box; this adjusts the viewer
  FXbool setBounds(const FXRange& box);
  
  /// Fit viewer to the given bounding box
  FXbool fitToBounds(const FXRange& box);
  
  %extend {
    /// Return the viewer's viewport
    FXViewport getViewport() const {
      FXViewport v;
      self->getViewport(v);
      return v;
    }
  }
  
#ifdef SWIGRUBY

  %extend {
    /// Translate eye-coordinate to screen coordinate
    VALUE eyeToScreen(FXVec e) {
      FXint sx, sy;
      self->eyeToScreen(sx, sy, e);
      VALUE point = rb_ary_new();
      rb_ary_push(point, INT2NUM(sx));
      rb_ary_push(point, INT2NUM(sy));
      return point;
    }
  }

#else

  %extend {
    /// Translate eye-coordinate to screen coordinate
    PyObject* eyeToScreen(const FXVec& e) {
      FXint sx,sy;
      self->eyeToScreen(sx,sy,e);
      FXbool doSave = FXPyRestoreThread();
      PyObject* tuple = PyTuple_New(2);
      if (tuple) {
        PyObject *obj1 = PyInt_FromLong(sx);
	if (!obj1 || PyTuple_SetItem(tuple, 0, obj1) < 0) {
	  Py_DECREF(tuple);
          FXPySaveThread(doSave);
	  return NULL;
	}
        PyObject *obj2 = PyInt_FromLong(sy);
	if (!obj2 || PyTuple_SetItem(tuple, 1, obj2) < 0) {
	  Py_DECREF(tuple);
          FXPySaveThread(doSave);
	  return NULL;
	}
      }
      FXPySaveThread(doSave);
      return tuple;
    }
  }

#endif
  
  /// Translate screen coordinate to eye coordinate at the given depth
  FXVec screenToEye(FXint sx,FXint sy,FXfloat eyez=0.0);
  
  /// Translate screen coordinate to eye coordinate at the target point depth
  FXVec screenToTarget(FXint sx,FXint sy);
  
  /// Translate world coordinate to eye coordinate
  FXVec worldToEye(FXVec w);
  
  /// Translate world coordinate to eye coordinate depth
  FXfloat worldToEyeZ(FXVec w);
  
  /// Translate eye coordinate to eye coordinate
  FXVec eyeToWorld(FXVec e); 
  
  /// Calculate world coordinate vector from screen movement
  FXVec worldVector(FXint fx,FXint fy,FXint tx,FXint ty);
  
  ///  Change default object material setting
  void setMaterial(const FXMaterial &mtl);
  
  %extend {
    /// Return default object material setting
    FXMaterial getMaterial() const {
      FXMaterial mtl;
      self->getMaterial(mtl);
      return mtl;
    }
  }
  
  /// Change camera field of view angle (in degrees)
  void setFieldOfView(FXdouble fv);
  
  /// Return camera field of view angle
  FXdouble getFieldOfView() const { return fov; }

  /// Change camera zoom factor
  void setZoom(FXdouble zm);
  
  /// Return camera zoom factor
  FXdouble getZoom() const;

  /// Change target point distance
  void setDistance(FXdouble ed);
  
  /// Return target point distance
  FXdouble getDistance() const;

  /// Change unequal model scaling factors
  void setScale(FXVec s);
  
  /// Return current scaling factors
  FXVec getScale() const;

  /// Change camera orientation from quaternion
  void setOrientation(FXQuat rot);
  
  /// Return current camera orientation quaternion
  FXQuat getOrientation() const;

  /// Change object center (tranlation)
  void setCenter(FXVec cntr);
  
  /// Return object center
  const FXVec& getCenter() const;

  /// Translate object center
  void translate(FXVec vec);
  
#ifdef SWIGRUBY
  %extend {
    /// Return boresight vector (an array of two arrays)
    VALUE getBoreVector(FXint sx,FXint sy) {
      FXVec point, dir;
      self->getBoreVector(sx, sy, point, dir);

      VALUE pointArray = rb_ary_new();
      rb_ary_push(pointArray, rb_float_new(point[0]));
      rb_ary_push(pointArray, rb_float_new(point[1]));
      rb_ary_push(pointArray, rb_float_new(point[2]));

      VALUE dirArray = rb_ary_new();
      rb_ary_push(dirArray, rb_float_new(dir[0]));
      rb_ary_push(dirArray, rb_float_new(dir[1]));
      rb_ary_push(dirArray, rb_float_new(dir[2]));

      VALUE results = rb_ary_new();
      rb_ary_push(results, pointArray);
      rb_ary_push(results, dirArray);

      return results;
    }
  }
#else
  %extend {
    /// Return boresight vector (a tuple of two lists)
    PyObject* getBoreVector(FXint sx,FXint sy) {
      FXVec point,dir;
      self->getBoreVector(sx,sy,point,dir);
      FXbool doSave = FXPyRestoreThread();
      PyObject* tuple = PyTuple_New(2);
      if (!tuple) {
        FXPySaveThread(doSave);
	return NULL;
      }

      PyObject* pointList = PyList_New(3);
      if (!pointList) {
	Py_DECREF(tuple);
        FXPySaveThread(doSave);
	return NULL;
      }

      PyObject* dirList = PyList_New(3);
      if (!dirList) {
	Py_DECREF(pointList);
	Py_DECREF(tuple);
        FXPySaveThread(doSave);
	return NULL;
      }

      for (int i=0; i<3; i++) {
        if (PyList_SetItem(pointList,i,Py_BuildValue("f",point[i])) < 0) {
	  Py_DECREF(pointList);
	  Py_DECREF(dirList);
	  Py_DECREF(tuple);
          FXPySaveThread(doSave);
	  return NULL;
	}
        if (PyList_SetItem(dirList,i,Py_BuildValue("f",dir[i])) < 0) {
	  Py_DECREF(pointList);
	  Py_DECREF(dirList);
	  Py_DECREF(tuple);
          FXPySaveThread(doSave);
	  return NULL;
	}
      }
      if (PyTuple_SetItem(tuple, 0, pointList) < 0) {
	Py_DECREF(dirList);
	Py_DECREF(tuple);
        FXPySaveThread(doSave);
	return NULL;
      }
      if (PyTuple_SetItem(tuple, 1, dirList) < 0) {
	Py_DECREF(tuple);
        FXPySaveThread(doSave);
	return NULL;
      }
      FXPySaveThread(doSave);
      return tuple;
    }
  }
#endif
  
  /// Return eyesight vector
  FXVec getEyeVector() const;
  
  /// Return eye position 
  FXVec getEyePosition() const;
  
  /// Change help text
  void setHelpText(const FXString& text);
  
  /// Return help text
  FXString getHelpText() const;

  /// Change tip text
  void setTipText(const FXString& text);
  
  /// Return tip text
  FXString getTipText() const;

  /// Change line offset
  void setOffset(FXdouble offs);
  
  /// Return line offset
  FXdouble getOffset() const;

  /// Return the current transformation matrix
  const FXHMat& getTransform() const;

  /// Return the inverse of the current transformation matrix
  const FXHMat& getInvTransform() const;

  /// Change the scene, i.e. the object being displayed.
  void setScene(FXGLObject* sc);
  
  /// Return the current scene object
  FXGLObject* getScene() const;

  /// Change selection
  void setSelection(FXGLObject* sel);

  /// Returns the selection
  FXGLObject* getSelection() const;

  /// Change the projection mode, PERSPECTIVE or PARALLEL
  void setProjection(FXuint proj);
  
  /// Return the projection mode
  FXuint getProjection() const;

  /// Lock the viewer, i.e. prevent mouse-based viewing operations
  void setViewLock(FXbool lock=TRUE);
  
  /// Return viewer lock status
  FXbool getViewLock() const;
  
  /// Change window background color
  void setBackgroundColor(const FXHVec& clr);
  
  /// Return window background color
  FXHVec getBackgroundColor() const;

  /// Change global ambient light color
  void setAmbientColor(const FXHVec& clr);
  
  /// Return global ambient light color
  FXHVec getAmbientColor() const;

#ifdef SWIGRUBY
  %extend {
  // Read the pixels off the screen as R,G,B tuples.
  VALUE readPixels(FXint x, FXint y, FXint w, FXint h) {
    FXuchar *buffer;
    VALUE pixels=Qnil;
    if(self->readPixels(buffer,x,y,w,h)){
      FXASSERT(sizeof(char)==sizeof(FXuchar));
      pixels=rb_str_new(reinterpret_cast<char*>(buffer),3*w*h);
      FXFREE(&buffer);
      }
    return pixels;
    }
  
  // Read the feedback buffer containing the current scene.
  VALUE readFeedback(FXint x, FXint y, FXint w, FXint h) {
    FXfloat *buffer;
    FXint used, size;
    if (self->readFeedback(buffer, used, size, x, y, w, h)) {
      VALUE results = rb_ary_new();
      for (FXint i = 0; i < used; i++)
        rb_ary_push(results, rb_float_new(buffer[i]));
      FXFREE(&buffer);
      return results;
    } else {
      return Qnil;
    }
  }
  }
#else
  %extend {
    PyObject *readPixels(FXint x,FXint y,FXint w,FXint h) {
      FXuchar *buffer;
      if(self->readPixels(buffer,x,y,w,h)){
	char *charBuffer;
	FXint nbytes=3*w*h;
	if(FXMALLOC(&charBuffer,char,nbytes)){
	  memcpy((void*)charBuffer,(void*)buffer,nbytes);
	  FXFREE(&buffer);
          FXbool doSave=FXPyRestoreThread();
	  PyObject *result=PyString_FromString(charBuffer);
          FXPySaveThread(doSave);
	  FXFREE(&charBuffer);
	  return result;
          }
	else{
	  FXFREE(&buffer);
          FXbool doSave=FXPyRestoreThread();
          PyErr_SetString(PyExc_MemoryError,"out of memory");
          FXPySaveThread(doSave);
	  return NULL;
	  }
        }
      else{
        FXbool doSave=FXPyRestoreThread();
        PyErr_SetString(PyExc_MemoryError,"out of memory");
        FXPySaveThread(doSave);
	return NULL;
        }
      }

    PyObject *readFeedback(FXint x,FXint y,FXint w,FXint h) {
      FXfloat *buffer;
      FXint used,size;
      if(self->readFeedback(buffer,used,size,x,y,w,h)){
        FXbool doSave=FXPyRestoreThread();
	PyObject *list=PyList_New(used);
	if(!list){
	  FXFREE(&buffer);
          FXPySaveThread(doSave);
	  return NULL;
	  }
	for(FXint i=0;i<used;i++){
	  PyObject *flt=PyFloat_FromDouble(buffer[i]);
	  if(!flt){
	    FXFREE(&buffer);
	    Py_DECREF(list);
            FXPySaveThread(doSave);
	    return NULL;
	    }
	  PyList_SetItem(list,i,flt);
	  }
        FXPySaveThread(doSave);
	FXFREE(&buffer);
	return list;
	}
      else{
        PyErr_SetString(PyExc_MemoryError,"out of memory");
	return NULL;
	}
      }
  }
#endif
  
#ifdef SWIGRUBY
  /**
  * Change hidden-surface feedback buffer sorting algorithm.
  * This can be used for move/draw printed output depth sorting.
  */  
  %extend {
    void setZSortFunc(VALUE proc){
      }
  }
#endif

#ifdef SWIGRUBY
  /// Return hidden surface sorting function.
  %extend {
    VALUE getZSortFunc() const {
      return Qnil;
      }
  }
#endif

  /** 
  * Change the maximum hits, i.e. the maximum size of the pick buffer.
  * When set to less than or equal to zero, picking is essentially turned off.
  */
  void setMaxHits(FXint maxh);

  /// Return maximum pickbuffer size
  FXint getMaxHits() const;

  /**
  * When drawing a GL object, if doesTurbo() is true, the object
  * may choose to perform a reduced complexity drawing as the user is
  * interactively manipulating; another update will be done later when
  * the full complexity drawing can be performed again.
  */
  FXbool doesTurbo() const;

  /// Return turbo mode setting
  FXbool getTurboMode() const;

  /// Set turbo mode
  void setTurboMode(FXbool turbo=TRUE);
  
  %extend {
    // Return light source settings
    FXLight getLight() const {
      FXLight lite;
      self->getLight(lite);
      return lite;
    }
  }
  
  /// Change light source settings
  void setLight(const FXLight& lite);

  /// Destructor
  virtual ~FXGLViewer();
  };


DECLARE_FXOBJECT_VIRTUALS(FXGLViewer)
DECLARE_FXID_VIRTUALS(FXGLViewer)
DECLARE_FXDRAWABLE_VIRTUALS(FXGLViewer)
DECLARE_FXWINDOW_VIRTUALS(FXGLViewer)
DECLARE_FXGLCANVAS_VIRTUALS(FXGLViewer)
DECLARE_FXGLVIEWER_VIRTUALS(FXGLViewer)

