/*

Copyright (C) 2008-2013 Michael Goffioul

This file is part of Octave.

Octave is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

Octave 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 General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING.  If not, see
<http://www.gnu.org/licenses/>.

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined (HAVE_OPENGL)

#include <iostream>

#include <lo-mappers.h>
#include "oct-locbuf.h"
#include "oct-refcount.h"
#include "gl-render.h"
#include "txt-eng.h"
#include "txt-eng-ft.h"

#define LIGHT_MODE GL_FRONT_AND_BACK

// Win32 API requires the CALLBACK attributes for
// GLU callback functions. Define it to empty on
// other platforms.
#ifndef CALLBACK
#define CALLBACK
#endif

class
opengl_texture
{
protected:
  class texture_rep
  {
  public:
    texture_rep (void)
      : id (), w (), h (), tw (), th (), tx (), ty (),
        valid (false), count (1)
    { }

    texture_rep (GLuint id_arg, int w_arg, int h_arg, int tw_arg, int th_arg)
      : id (id_arg), w (w_arg), h (h_arg), tw (tw_arg), th (th_arg),
        tx (double(w)/tw), ty (double(h)/th), valid (true),
        count (1) { }

    ~texture_rep (void)
    {
      if (valid)
        glDeleteTextures (1, &id);
    }

    void bind (int mode) const
    { if (valid) glBindTexture (mode, id); }

    void tex_coord (double q, double r) const
    { if (valid) glTexCoord2d (q*tx, r*ty); }

    GLuint id;
    int w, h;
    int tw, th;
    double tx, ty;
    bool valid;
    octave_refcount<int> count;
  };

  texture_rep *rep;

private:
  opengl_texture (texture_rep *_rep) : rep (_rep) { }

public:
  opengl_texture (void) : rep (new texture_rep ()) { }

  opengl_texture (const opengl_texture& tx)
    : rep (tx.rep)
  {
    rep->count++;
  }

  ~opengl_texture (void)
  {
    if (--rep->count == 0)
      delete rep;
  }

  opengl_texture& operator = (const opengl_texture& tx)
  {
    if (--rep->count == 0)
      delete rep;

    rep = tx.rep;
    rep->count++;

    return *this;
  }

  static opengl_texture create (const octave_value& data);

  void bind (int mode = GL_TEXTURE_2D) const
  { rep->bind (mode); }

  void tex_coord (double q, double r) const
  { rep->tex_coord (q, r); }

  bool is_valid (void) const
  { return rep->valid; }
};

static int
next_power_of_2 (int n)
{
  int m = 1;

  while (m < n && m < std::numeric_limits<int>::max ())
    m <<= 1;

  return m;
}

opengl_texture
opengl_texture::create (const octave_value& data)
{
  opengl_texture retval;

  dim_vector dv (data.dims ());

  // Expect RGB data
  if (dv.length () == 3 && dv(2) == 3)
    {
      // FIXME: dim_vectors hold octave_idx_type values.
      //        Should we check for dimensions larger than intmax?
      int h = dv(0), w = dv(1), tw, th;
      GLuint id;
      bool ok = true;

      tw = next_power_of_2 (w);
      th = next_power_of_2 (w);

      glGenTextures (1, &id);
      glBindTexture (GL_TEXTURE_2D, id);

      if (data.is_double_type ())
        {
          const NDArray xdata = data.array_value ();

          OCTAVE_LOCAL_BUFFER (float, a, (3*tw*th));

          for (int i = 0; i < h; i++)
            {
              for (int j = 0, idx = i*tw*3; j < w; j++, idx += 3)
                {
                  a[idx]   = xdata(i,j,0);
                  a[idx+1] = xdata(i,j,1);
                  a[idx+2] = xdata(i,j,2);
                }
            }

          glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0, GL_RGB, GL_FLOAT, a);
        }
      else if (data.is_uint8_type ())
        {
          const uint8NDArray xdata = data.uint8_array_value ();

          OCTAVE_LOCAL_BUFFER (octave_uint8, a, (3*tw*th));

          for (int i = 0; i < h; i++)
            {
              for (int j = 0, idx = i*tw*3; j < w; j++, idx += 3)
                {
                  a[idx]   = xdata(i,j,0);
                  a[idx+1] = xdata(i,j,1);
                  a[idx+2] = xdata(i,j,2);
                }
            }

          glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0,
                        GL_RGB, GL_UNSIGNED_BYTE, a);
        }
      else
        {
          ok = false;
          warning ("opengl_texture::create: invalid texture data type (expected double or uint8)");
        }

      if (ok)
        {
          glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
          glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

          if (glGetError () != GL_NO_ERROR)
            warning ("opengl_texture::create: OpenGL error while generating texture data");
          else
            retval = opengl_texture (new texture_rep (id, w, h, tw, th));
        }
    }
  else
    warning ("opengl_texture::create: invalid texture data size");

  return retval;
}

class
opengl_tesselator
{
public:
#if defined (HAVE_FRAMEWORK_OPENGL) && defined (HAVE_GLUTESSCALLBACK_THREEDOTS)
  typedef GLvoid (CALLBACK *fcn) (...);
#else
  typedef void (CALLBACK *fcn) (void);
#endif

public:

  opengl_tesselator (void) : glu_tess (0), fill () { init (); }

  virtual ~opengl_tesselator (void)
  { if (glu_tess) gluDeleteTess (glu_tess); }

  void begin_polygon (bool filled = true)
  {
    gluTessProperty (glu_tess, GLU_TESS_BOUNDARY_ONLY,
                     (filled ? GL_FALSE : GL_TRUE));
    fill = filled;
    gluTessBeginPolygon (glu_tess, this);
  }

  void end_polygon (void) const
  { gluTessEndPolygon (glu_tess); }

  void begin_contour (void) const
  { gluTessBeginContour (glu_tess); }

  void end_contour (void) const
  { gluTessEndContour (glu_tess); }

  void add_vertex (double *loc, void *data) const
  { gluTessVertex (glu_tess, loc, data); }

protected:
  virtual void begin (GLenum /*type*/) { }

  virtual void end (void) { }

  virtual void vertex (void * /*data*/) { }

  virtual void combine (GLdouble [3] /*c*/, void * [4] /*data*/,
                        GLfloat  [4] /*w*/, void ** /*out_data*/) { }

  virtual void edge_flag (GLboolean /*flag*/) { }

  virtual void error (GLenum err)
  { ::error ("OpenGL tesselation error (%d)", err); }

  virtual void init (void)
  {
    glu_tess = gluNewTess ();

    gluTessCallback (glu_tess, GLU_TESS_BEGIN_DATA,
                     reinterpret_cast<fcn> (tess_begin));
    gluTessCallback (glu_tess, GLU_TESS_END_DATA,
                     reinterpret_cast<fcn> (tess_end));
    gluTessCallback (glu_tess, GLU_TESS_VERTEX_DATA,
                     reinterpret_cast<fcn> (tess_vertex));
    gluTessCallback (glu_tess, GLU_TESS_COMBINE_DATA,
                     reinterpret_cast<fcn> (tess_combine));
    gluTessCallback (glu_tess, GLU_TESS_EDGE_FLAG_DATA,
                     reinterpret_cast<fcn> (tess_edge_flag));
    gluTessCallback (glu_tess, GLU_TESS_ERROR_DATA,
                     reinterpret_cast<fcn> (tess_error));
  }

  bool is_filled (void) const { return fill; }

private:
  static void CALLBACK tess_begin (GLenum type, void *t)
  { reinterpret_cast<opengl_tesselator *> (t)->begin (type); }

  static void CALLBACK tess_end (void *t)
  { reinterpret_cast<opengl_tesselator *> (t)->end (); }

  static void CALLBACK tess_vertex (void *v, void *t)
  { reinterpret_cast<opengl_tesselator *> (t)->vertex (v); }

  static void CALLBACK tess_combine (GLdouble c[3], void *v[4], GLfloat w[4],
                                     void **out,  void *t)
  { reinterpret_cast<opengl_tesselator *> (t)->combine (c, v, w, out); }

  static void CALLBACK tess_edge_flag (GLboolean flag, void *t)
  { reinterpret_cast<opengl_tesselator *> (t)->edge_flag (flag); }

  static void CALLBACK tess_error (GLenum err, void *t)
  { reinterpret_cast<opengl_tesselator *> (t)->error (err); }

private:

  // No copying!

  opengl_tesselator (const opengl_tesselator&);

  opengl_tesselator operator = (const opengl_tesselator&);

  GLUtesselator *glu_tess;
  bool fill;
};

class
vertex_data
{
public:
  class vertex_data_rep
  {
  public:
    Matrix coords;
    Matrix color;
    Matrix normal;
    double alpha;
    float ambient;
    float diffuse;
    float specular;
    float specular_exp;

    // reference counter
    octave_refcount<int> count;

    vertex_data_rep (void)
      : coords (), color (), normal (), alpha (),
        ambient (), diffuse (), specular (), specular_exp (),count (1) { }

    vertex_data_rep (const Matrix& c, const Matrix& col, const Matrix& n,
                     double a, float as, float ds, float ss, float se)
      : coords (c), color (col), normal (n), alpha (a),
        ambient (as), diffuse (ds), specular (ss), specular_exp (se),
        count (1) { }
  };

private:
  vertex_data_rep *rep;

  vertex_data_rep *nil_rep (void) const
  {
    static vertex_data_rep *nr = new vertex_data_rep ();

    return nr;
  }

public:
  vertex_data (void) : rep (nil_rep ())
  { rep->count++; }

  vertex_data (const vertex_data& v) : rep (v.rep)
  { rep->count++; }

  vertex_data (const Matrix& c, const Matrix& col, const Matrix& n,
               double a, float as, float ds, float ss, float se)
    : rep (new vertex_data_rep (c, col, n, a, as, ds, ss, se))
  { }

  vertex_data (vertex_data_rep *new_rep)
    : rep (new_rep) { }

  ~vertex_data (void)
  {
    if (--rep->count == 0)
      delete rep;
  }

  vertex_data& operator = (const vertex_data& v)
  {
    if (--rep->count == 0)
      delete rep;

    rep = v.rep;
    rep->count++;

    return *this;
  }

  vertex_data_rep *get_rep (void) const { return rep; }
};

class
opengl_renderer::patch_tesselator : public opengl_tesselator
{
public:
  patch_tesselator (opengl_renderer *r, int cmode, int lmode, int idx = 0)
    : opengl_tesselator (), renderer (r),
      color_mode (cmode), light_mode (lmode), index (idx),
      first (true), tmp_vdata ()
  { }

protected:
  void begin (GLenum type)
  {
    //printf ("patch_tesselator::begin (%d)\n", type);
    first = true;

    if (color_mode == 2 || light_mode == 2)
      glShadeModel (GL_SMOOTH);
    else
      glShadeModel (GL_FLAT);

    if (is_filled ())
      renderer->set_polygon_offset (true, 1+index);

    glBegin (type);
  }

  void end (void)
  {
    //printf ("patch_tesselator::end\n");
    glEnd ();
    renderer->set_polygon_offset (false);
  }

  void vertex (void *data)
  {
    vertex_data::vertex_data_rep *v
      = reinterpret_cast<vertex_data::vertex_data_rep *> (data);
    //printf ("patch_tesselator::vertex (%g, %g, %g)\n", v->coords(0), v->coords(1), v->coords(2));

    // FIXME: why did I need to keep the first vertex of the face
    // in JHandles? I think it's related to the fact that the
    // tessellation process might re-order the vertices, such that
    // the first one you get here might not be the first one of the face;
    // but I can't figure out the actual reason.
    if (color_mode > 0 && (first || color_mode == 2))
      {
        Matrix col = v->color;

        if (col.numel () == 3)
          {
            glColor3dv (col.data ());
            if (light_mode > 0)
              {
                float buf[4] = { 0, 0, 0, 1 };

                for (int k = 0; k < 3; k++)
                  buf[k] = (v->ambient * col(k));
                glMaterialfv (LIGHT_MODE, GL_AMBIENT, buf);

                for (int k = 0; k < 3; k++)
                  buf[k] = (v->diffuse * col(k));
                glMaterialfv (LIGHT_MODE, GL_AMBIENT, buf);
              }
          }
      }

    if (light_mode > 0 && (first || light_mode == 2))
      glNormal3dv (v->normal.data ());

    glVertex3dv (v->coords.data ());

    first = false;
  }

  void combine (GLdouble xyz[3], void *data[4], GLfloat w[4],
                void **out_data)
  {
    //printf ("patch_tesselator::combine\n");

    vertex_data::vertex_data_rep *v[4];
    int vmax = 4;

    for (int i = 0; i < 4; i++)
      {
        v[i] = reinterpret_cast<vertex_data::vertex_data_rep *> (data[i]);

        if (vmax == 4 && ! v[i])
          vmax = i;
      }

    Matrix vv (1, 3, 0.0);
    Matrix cc;
    Matrix nn (1, 3, 0.0);
    double aa = 0.0;

    vv(0) = xyz[0];
    vv(1) = xyz[1];
    vv(2) = xyz[2];

    if (v[0]->color.numel ())
      {
        cc.resize (1, 3, 0.0);
        for (int ic = 0; ic < 3; ic++)
          for (int iv = 0; iv < vmax; iv++)
            cc(ic) += (w[iv] * v[iv]->color (ic));
      }

    if (v[0]->normal.numel () > 0)
      {
        for (int in = 0; in < 3; in++)
          for (int iv = 0; iv < vmax; iv++)
            nn(in) += (w[iv] * v[iv]->normal (in));
      }

    for (int iv = 0; iv < vmax; iv++)
      aa += (w[iv] * v[iv]->alpha);

    vertex_data new_v (vv, cc, nn, aa, v[0]->ambient, v[0]->diffuse,
                       v[0]->specular, v[0]->specular_exp);
    tmp_vdata.push_back (new_v);

    *out_data = new_v.get_rep ();
  }

private:

  // No copying!

  patch_tesselator (const patch_tesselator&);

  patch_tesselator& operator = (const patch_tesselator&);

  opengl_renderer *renderer;
  int color_mode;       // 0: uni,  1: flat, 2: interp
  int light_mode;       // 0: none, 1: flat, 2: gouraud
  int index;
  bool first;
  std::list<vertex_data> tmp_vdata;
};

void
opengl_renderer::draw (const graphics_object& go, bool toplevel)
{
  if (! go.valid_object ())
    return;

  const base_properties& props = go.get_properties ();

  if (! toolkit)
    toolkit = props.get_toolkit ();

  if (go.isa ("figure"))
    draw_figure (dynamic_cast<const figure::properties&> (props));
  else if (go.isa ("axes"))
    draw_axes (dynamic_cast<const axes::properties&> (props));
  else if (go.isa ("line"))
    draw_line (dynamic_cast<const line::properties&> (props));
  else if (go.isa ("surface"))
    draw_surface (dynamic_cast<const surface::properties&> (props));
  else if (go.isa ("patch"))
    draw_patch (dynamic_cast<const patch::properties&> (props));
  else if (go.isa ("hggroup"))
    draw_hggroup (dynamic_cast<const hggroup::properties&> (props));
  else if (go.isa ("text"))
    draw_text (dynamic_cast<const text::properties&> (props));
  else if (go.isa ("image"))
    draw_image (dynamic_cast<const image::properties&> (props));
  else if (go.isa ("uimenu") || go.isa ("uicontrol")
           || go.isa ("uicontextmenu") || go.isa ("uitoolbar")
           || go.isa ("uipushtool") || go.isa ("uitoggletool"))
    /* SKIP */;
  else if (go.isa ("uipanel"))
    {
      if (toplevel)
        draw_uipanel (dynamic_cast<const uipanel::properties&> (props), go);
    }
  else
    {
      warning ("opengl_renderer: cannot render object of type '%s'",
               props.graphics_object_name ().c_str ());
    }
}

void
opengl_renderer::draw_figure (const figure::properties& props)
{
  // Initialize OpenGL context

  init_gl_context (props.is___enhanced__ (), props.get_color_rgb ());

  // Draw children

  draw (props.get_all_children (), false);
}

void
opengl_renderer::draw_uipanel (const uipanel::properties& props,
                               const graphics_object& go)
{
  graphics_object fig = go.get_ancestor ("figure");
  const figure::properties& figProps =
    dynamic_cast<const figure::properties&> (fig.get_properties ());

  // Initialize OpenGL context

  init_gl_context (figProps.is___enhanced__ (),
                   props.get_backgroundcolor_rgb ());

  // Draw children

  draw (props.get_all_children (), false);
}

void
opengl_renderer::init_gl_context (bool enhanced, const Matrix& c)
{
  // Initialize OpenGL context

  glEnable (GL_DEPTH_TEST);
  glDepthFunc (GL_LEQUAL);
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glAlphaFunc (GL_GREATER, 0.0f);
  glEnable (GL_NORMALIZE);

  if (enhanced)
    {
      glEnable (GL_BLEND);
      glEnable (GL_LINE_SMOOTH);
    }
  else
    {
      glDisable (GL_BLEND);
      glDisable (GL_LINE_SMOOTH);
    }

  // Clear background

  if (c.length () >= 3)
    {
      glClearColor (c(0), c(1), c(2), 1);
      glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }
}

void
opengl_renderer::render_grid (const std::string& gridstyle,
                              const Matrix& ticks, double lim1, double lim2,
                              double p1, double p1N, double p2, double p2N,
                              int xyz, bool is_3D)
{
  set_linestyle (gridstyle, true);
  glBegin (GL_LINES);
  for (int i = 0; i < ticks.numel (); i++)
    {
      double val = ticks(i);
      if (lim1 <= val && val <= lim2)
        {
          if (xyz == 0) // X
            {
              glVertex3d (val, p1N, p2);
              glVertex3d (val, p1, p2);
              if (is_3D)
                {
                  glVertex3d (val, p1, p2N);
                  glVertex3d (val, p1, p2);
                }
            }
          else if (xyz == 1) // Y
            {
              glVertex3d (p1N, val, p2);
              glVertex3d (p1, val, p2);
              if (is_3D)
                {
                  glVertex3d (p1, val, p2N);
                  glVertex3d (p1, val, p2);
                }
            }
          else if (xyz == 2) // Z
            {
              glVertex3d (p1N, p2, val);
              glVertex3d (p1, p2, val);
              glVertex3d (p1, p2N, val);
              glVertex3d (p1, p2, val);
            }
        }
    }
  glEnd ();
  set_linestyle ("-", true);
}

void
opengl_renderer::render_tickmarks (const Matrix& ticks,
                                   double lim1, double lim2,
                                   double p1, double p1N,
                                   double p2, double p2N,
                                   double dx, double dy, double dz,
                                   int xyz, bool mirror)
{
  glBegin (GL_LINES);

  for (int i = 0; i < ticks.numel (); i++)
    {
      double val = ticks(i);

      if (lim1 <= val && val <= lim2)
        {
          if (xyz == 0) // X
            {
              glVertex3d (val, p1, p2);
              glVertex3d (val, p1+dy, p2+dz);
              if (mirror)
                {
                  glVertex3d (val, p1N, p2N);
                  glVertex3d (val, p1N-dy, p2N-dz);
                }
            }
          else if (xyz == 1) // Y
            {
              glVertex3d (p1, val, p2);
              glVertex3d (p1+dx, val, p2+dz);
              if (mirror)
                {
                  glVertex3d (p1N, val, p2N);
                  glVertex3d (p1N-dx, val, p2N-dz);
                }
            }
          else if (xyz == 2) // Z
            {
              glVertex3d (p1, p2, val);
              glVertex3d (p1+dx, p2+dy, val);
              if (mirror)
                {
                  glVertex3d (p1N, p2N, val);
                  glVertex3d (p1N-dx, p2N-dy, val);
                }
            }
        }
    }

  glEnd ();
}

void
opengl_renderer::render_ticktexts (const Matrix& ticks,
                                   const string_vector& ticklabels,
                                   double lim1, double lim2,
                                   double p1, double p2,
                                   int xyz, int ha, int va,
                                   int& wmax, int& hmax)
{
  int nticks  = ticks.numel ();
  int nlabels = ticklabels.numel ();

  if (nlabels == 0)
    return;

  for (int i = 0; i < nticks; i++)
    {
      double val = ticks(i);

      if (lim1 <= val && val <= lim2)
        {
          Matrix b;

          std::string label (ticklabels(i % nlabels));
          label.erase (0, label.find_first_not_of (" "));
          label = label.substr (0, label.find_last_not_of (" ")+1);

          // FIXME: as tick text is transparent, shouldn't it be
          //        drawn after axes object, for correct rendering?
          if (xyz == 0) // X
            {
              b = render_text (label, val, p1, p2, ha, va);
            }
          else if (xyz == 1) // Y
            {
              b = render_text (label, p1, val, p2, ha, va);
            }
          else if (xyz == 2) // Z
            {
              b = render_text (label, p1, p2, val, ha, va);
            }

          wmax = std::max (wmax, static_cast<int> (b(2)));
          hmax = std::max (hmax, static_cast<int> (b(3)));
        }
    }
}

void
opengl_renderer::setup_opengl_transformation (const axes::properties& props)
{
  // setup OpenGL transformation

  Matrix x_zlim = props.get_transform_zlim ();

  xZ1 = x_zlim(0)-(x_zlim(1)-x_zlim(0))/2;
  xZ2 = x_zlim(1)+(x_zlim(1)-x_zlim(0))/2;

  Matrix x_mat1 = props.get_opengl_matrix_1 ();
  Matrix x_mat2 = props.get_opengl_matrix_2 ();

#if defined (HAVE_FRAMEWORK_OPENGL)
  GLint vw[4];
#else
  int vw[4];
#endif

  glGetIntegerv (GL_VIEWPORT, vw);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
  glScaled (1, 1, -1);
  glMultMatrixd (x_mat1.data ());
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho (0, vw[2], vw[3], 0, xZ1, xZ2);
  glMultMatrixd (x_mat2.data ());
  glMatrixMode (GL_MODELVIEW);

  glClear (GL_DEPTH_BUFFER_BIT);

  glDisable (GL_LINE_SMOOTH);

  // store axes transformation data

  xform = props.get_transform ();
}

void
opengl_renderer::draw_axes_planes (const axes::properties& props)
{
  double xPlane = props.get_xPlane ();
  double yPlane = props.get_yPlane ();
  double zPlane = props.get_zPlane ();
  double xPlaneN = props.get_xPlaneN ();
  double yPlaneN = props.get_yPlaneN ();
  double zPlaneN = props.get_zPlaneN ();

  // Axes planes
  Matrix axe_color = props.get_color_rgb ();
  if (axe_color.numel () > 0 && props.is_visible ())
    {
      set_color (axe_color);
      set_polygon_offset (true, 2.5);

      glBegin (GL_QUADS);

      // X plane
      glVertex3d (xPlane, yPlaneN, zPlaneN);
      glVertex3d (xPlane, yPlane, zPlaneN);
      glVertex3d (xPlane, yPlane, zPlane);
      glVertex3d (xPlane, yPlaneN, zPlane);

      // Y plane
      glVertex3d (xPlaneN, yPlane, zPlaneN);
      glVertex3d (xPlane, yPlane, zPlaneN);
      glVertex3d (xPlane, yPlane, zPlane);
      glVertex3d (xPlaneN, yPlane, zPlane);

      // Z plane
      glVertex3d (xPlaneN, yPlaneN, zPlane);
      glVertex3d (xPlane, yPlaneN, zPlane);
      glVertex3d (xPlane, yPlane, zPlane);
      glVertex3d (xPlaneN, yPlane, zPlane);

      glEnd ();

      set_polygon_offset (false);
    }
}

void
opengl_renderer::draw_axes_boxes (const axes::properties& props)
{
  bool xySym = props.get_xySym ();
  double xPlane = props.get_xPlane ();
  double yPlane = props.get_yPlane ();
  double zPlane = props.get_zPlane ();
  double xPlaneN = props.get_xPlaneN ();
  double yPlaneN = props.get_yPlaneN ();
  double zPlaneN = props.get_zPlaneN ();
  double xpTick = props.get_xpTick ();
  double ypTick = props.get_ypTick ();
  double zpTick = props.get_zpTick ();
  double xpTickN = props.get_xpTickN ();
  double ypTickN = props.get_ypTickN ();
  double zpTickN = props.get_zpTickN ();

  bool plotyy = (props.has_property ("__plotyy_axes__"));

  // Axes box

  set_linestyle ("-", true);
  set_linewidth (props.get_linewidth ());

  if (props.is_visible ())
    {
      glBegin (GL_LINES);

      // X box
      set_color (props.get_xcolor_rgb ());
      glVertex3d (xPlaneN, ypTick, zpTick);
      glVertex3d (xPlane, ypTick, zpTick);

      if (props.is_box ())
        {
          glVertex3d (xPlaneN, ypTickN, zpTick);
          glVertex3d (xPlane, ypTickN, zpTick);
          glVertex3d (xPlaneN, ypTickN, zpTickN);
          glVertex3d (xPlane, ypTickN, zpTickN);
          glVertex3d (xPlaneN, ypTick, zpTickN);
          glVertex3d (xPlane, ypTick, zpTickN);
        }

      // Y box
      set_color (props.get_ycolor_rgb ());
      glVertex3d (xpTick, yPlaneN, zpTick);
      glVertex3d (xpTick, yPlane, zpTick);

      if (props.is_box () && ! plotyy)
        {
          glVertex3d (xpTickN, yPlaneN, zpTick);
          glVertex3d (xpTickN, yPlane, zpTick);
          glVertex3d (xpTickN, yPlaneN, zpTickN);
          glVertex3d (xpTickN, yPlane, zpTickN);
          glVertex3d (xpTick, yPlaneN, zpTickN);
          glVertex3d (xpTick, yPlane, zpTickN);
        }

      // Z box
      set_color (props.get_zcolor_rgb ());

      if (xySym)
        {
          glVertex3d (xPlaneN, yPlane, zPlaneN);
          glVertex3d (xPlaneN, yPlane, zPlane);
        }
      else
        {
          glVertex3d (xPlane, yPlaneN, zPlaneN);
          glVertex3d (xPlane, yPlaneN, zPlane);
        }

      if (props.is_box ())
        {
          glVertex3d (xPlane, yPlane, zPlaneN);
          glVertex3d (xPlane, yPlane, zPlane);

          if (xySym)
            {
              glVertex3d (xPlane, yPlaneN, zPlaneN);
              glVertex3d (xPlane, yPlaneN, zPlane);
            }
          else
            {
              glVertex3d (xPlaneN, yPlane, zPlaneN);
              glVertex3d (xPlaneN, yPlane, zPlane);
            }

          glVertex3d (xPlaneN, yPlaneN, zPlaneN);
          glVertex3d (xPlaneN, yPlaneN, zPlane);
        }

      glEnd ();
    }
}

void
opengl_renderer::draw_axes_x_grid (const axes::properties& props)
{
  int xstate = props.get_xstate ();
  int zstate = props.get_zstate ();
  bool x2Dtop = props.get_x2Dtop ();
  bool layer2Dtop = props.get_layer2Dtop ();
  bool xyzSym = props.get_xyzSym ();
  bool nearhoriz = props.get_nearhoriz ();
  double xticklen = props.get_xticklen ();
  double xtickoffset = props.get_xtickoffset ();
  double fy = props.get_fy ();
  double fz = props.get_fz ();
  double x_min = props.get_x_min ();
  double x_max = props.get_x_max ();
  double yPlane = props.get_yPlane ();
  double yPlaneN = props.get_yPlaneN ();
  double ypTick = props.get_ypTick ();
  double ypTickN = props.get_ypTickN ();
  double zPlane = props.get_zPlane ();
  double zPlaneN = props.get_zPlaneN ();
  double zpTick = props.get_zpTick ();
  double zpTickN = props.get_zpTickN ();

  // X grid

  if (props.is_visible () && xstate != AXE_DEPTH_DIR)
    {
      std::string gridstyle = props.get_gridlinestyle ();
      std::string minorgridstyle = props.get_minorgridlinestyle ();
      bool do_xgrid = (props.is_xgrid () && (gridstyle != "none"));
      bool do_xminorgrid = (props.is_xminorgrid ()
                            && (minorgridstyle != "none"));
      bool do_xminortick = props.is_xminortick ();
      Matrix xticks = xform.xscale (props.get_xtick ().matrix_value ());
      Matrix xmticks = xform.xscale (props.get_xmtick ().matrix_value ());
      string_vector xticklabels = props.get_xticklabel ().all_strings ();
      int wmax = 0, hmax = 0;
      bool tick_along_z = nearhoriz || xisinf (fy);
      bool mirror = props.is_box () && xstate != AXE_ANY_DIR;

      set_color (props.get_xcolor_rgb ());

      // grid lines
      if (do_xgrid)
        render_grid (gridstyle, xticks, x_min, x_max,
                     yPlane, yPlaneN, layer2Dtop ? zPlaneN : zPlane,
                     zPlaneN, 0, (zstate != AXE_DEPTH_DIR));

      // tick marks
      if (tick_along_z)
        {
          render_tickmarks (xticks, x_min, x_max, ypTick, ypTick,
                            zpTick, zpTickN, 0., 0.,
                            signum (zpTick-zpTickN)*fz*xticklen,
                            0, mirror);
        }
      else
        {
          render_tickmarks (xticks, x_min, x_max, ypTick, ypTickN,
                            zpTick, zpTick, 0.,
                            signum (ypTick-ypTickN)*fy*xticklen,
                            0., 0, mirror);
        }

      // tick texts
      if (xticklabels.numel () > 0)
        {
          int halign = (xstate == AXE_HORZ_DIR ? 1 : (xyzSym ? 0 : 2));
          int valign = (xstate == AXE_VERT_DIR ? 1 : (x2Dtop ? 0 : 2));

          if (tick_along_z)
            render_ticktexts (xticks, xticklabels, x_min, x_max, ypTick,
                              zpTick+signum (zpTick-zpTickN)*fz*xtickoffset,
                              0, halign, valign, wmax, hmax);
          else
            render_ticktexts (xticks, xticklabels, x_min, x_max,
                              ypTick+signum (ypTick-ypTickN)*fy*xtickoffset,
                              zpTick, 0, halign, valign, wmax, hmax);
        }

      // minor grid lines
      if (do_xminorgrid)
        render_grid (minorgridstyle, xmticks, x_min, x_max,
                     yPlane, yPlaneN, layer2Dtop ? zPlaneN : zPlane,
                     zPlaneN, 0, (zstate != AXE_DEPTH_DIR));

      // minor tick marks
      if (do_xminortick)
        {
          if (tick_along_z)
            render_tickmarks (xmticks, x_min, x_max, ypTick, ypTick,
                              zpTick, zpTickN, 0., 0.,
                              signum (zpTick-zpTickN)*fz*xticklen/2,
                              0, mirror);
          else
            render_tickmarks (xmticks, x_min, x_max, ypTick, ypTickN,
                              zpTick, zpTick, 0.,
                              signum (ypTick-ypTickN)*fy*xticklen/2,
                              0., 0, mirror);
        }

      gh_manager::get_object (props.get_xlabel ()).set ("visible", "on");
    }
  else
    gh_manager::get_object (props.get_xlabel ()).set ("visible", "off");
}

void
opengl_renderer::draw_axes_y_grid (const axes::properties& props)
{
  int ystate = props.get_ystate ();
  int zstate = props.get_zstate ();
  bool y2Dright = props.get_y2Dright ();
  bool layer2Dtop = props.get_layer2Dtop ();
  bool xyzSym = props.get_xyzSym ();
  bool nearhoriz = props.get_nearhoriz ();
  double yticklen = props.get_yticklen ();
  double ytickoffset = props.get_ytickoffset ();
  double fx = props.get_fx ();
  double fz = props.get_fz ();
  double xPlane = props.get_xPlane ();
  double xPlaneN = props.get_xPlaneN ();
  double xpTick = props.get_xpTick ();
  double xpTickN = props.get_xpTickN ();
  double y_min = props.get_y_min ();
  double y_max = props.get_y_max ();
  double zPlane = props.get_zPlane ();
  double zPlaneN = props.get_zPlaneN ();
  double zpTick = props.get_zpTick ();
  double zpTickN = props.get_zpTickN ();

  // Y grid

  if (ystate != AXE_DEPTH_DIR && props.is_visible ())
    {
      std::string gridstyle = props.get_gridlinestyle ();
      std::string minorgridstyle = props.get_minorgridlinestyle ();
      bool do_ygrid = (props.is_ygrid () && (gridstyle != "none"));
      bool do_yminorgrid = (props.is_yminorgrid ()
                            && (minorgridstyle != "none"));
      bool do_yminortick = props.is_yminortick ();
      Matrix yticks = xform.yscale (props.get_ytick ().matrix_value ());
      Matrix ymticks = xform.yscale (props.get_ymtick ().matrix_value ());
      string_vector yticklabels = props.get_yticklabel ().all_strings ();
      int wmax = 0, hmax = 0;
      bool tick_along_z = nearhoriz || xisinf (fx);
      bool mirror = props.is_box () && ystate != AXE_ANY_DIR
                    && (! props.has_property ("__plotyy_axes__"));

      set_color (props.get_ycolor_rgb ());

      // grid lines
      if (do_ygrid)
        render_grid (gridstyle, yticks, y_min, y_max,
                     xPlane, xPlaneN, layer2Dtop ? zPlaneN : zPlane,
                     zPlaneN, 1, (zstate != AXE_DEPTH_DIR));

      // tick marks
      if (tick_along_z)
        render_tickmarks (yticks, y_min, y_max, xpTick, xpTick,
                          zpTick, zpTickN, 0., 0.,
                          signum (zpTick-zpTickN)*fz*yticklen,
                          1, mirror);
      else
        render_tickmarks (yticks, y_min, y_max, xpTick, xpTickN,
                          zpTick, zpTick,
                          signum (xPlaneN-xPlane)*fx*yticklen,
                          0., 0., 1, mirror);

      // tick texts
      if (yticklabels.numel () > 0)
        {
          int halign = (ystate == AXE_HORZ_DIR
                        ? 1 : (!xyzSym || y2Dright ? 0 : 2));
          int valign = (ystate == AXE_VERT_DIR ? 1 : 2);

          if (tick_along_z)
            render_ticktexts (yticks, yticklabels, y_min, y_max, xpTick,
                              zpTick+signum (zpTick-zpTickN)*fz*ytickoffset,
                              1, halign, valign, wmax, hmax);
          else
            render_ticktexts (yticks, yticklabels, y_min, y_max,
                              xpTick+signum (xpTick-xpTickN)*fx*ytickoffset,
                              zpTick, 1, halign, valign, wmax, hmax);
        }

      // minor grid lines
      if (do_yminorgrid)
        render_grid (minorgridstyle, ymticks, y_min, y_max,
                     xPlane, xPlaneN, layer2Dtop ? zPlaneN : zPlane,
                     zPlaneN, 1, (zstate != AXE_DEPTH_DIR));

      // minor tick marks
      if (do_yminortick)
        {
          if (tick_along_z)
            render_tickmarks (ymticks, y_min, y_max, xpTick, xpTick,
                              zpTick, zpTickN, 0., 0.,
                              signum (zpTick-zpTickN)*fz*yticklen/2,
                              1, mirror);
          else
            render_tickmarks (ymticks, y_min, y_max, xpTick, xpTickN,
                              zpTick, zpTick,
                              signum (xpTick-xpTickN)*fx*yticklen/2,
                              0., 0., 1, mirror);
        }

      gh_manager::get_object (props.get_ylabel ()).set ("visible", "on");
    }
  else
    gh_manager::get_object (props.get_ylabel ()).set ("visible", "off");
}

void
opengl_renderer::draw_axes_z_grid (const axes::properties& props)
{
  int zstate = props.get_zstate ();
  bool xySym = props.get_xySym ();
  bool zSign = props.get_zSign ();
  double zticklen = props.get_zticklen ();
  double ztickoffset = props.get_ztickoffset ();
  double fx = props.get_fx ();
  double fy = props.get_fy ();
  double xPlane = props.get_xPlane ();
  double xPlaneN = props.get_xPlaneN ();
  double yPlane = props.get_yPlane ();
  double yPlaneN = props.get_yPlaneN ();
  double z_min = props.get_z_min ();
  double z_max = props.get_z_max ();

  // Z Grid

  if (zstate != AXE_DEPTH_DIR && props.is_visible ())
    {
      std::string gridstyle = props.get_gridlinestyle ();
      std::string minorgridstyle = props.get_minorgridlinestyle ();
      bool do_zgrid = (props.is_zgrid () && (gridstyle != "none"));
      bool do_zminorgrid = (props.is_zminorgrid ()
                            && (minorgridstyle != "none"));
      bool do_zminortick = props.is_zminortick ();
      Matrix zticks = xform.zscale (props.get_ztick ().matrix_value ());
      Matrix zmticks = xform.zscale (props.get_zmtick ().matrix_value ());
      string_vector zticklabels = props.get_zticklabel ().all_strings ();
      int wmax = 0, hmax = 0;
      bool mirror = props.is_box () && zstate != AXE_ANY_DIR;

      set_color (props.get_zcolor_rgb ());

      // grid lines
      if (do_zgrid)
        render_grid (gridstyle, zticks, z_min, z_max,
                     xPlane, xPlaneN, yPlane, yPlaneN, 2, true);

      // tick marks
      if (xySym)
        {
          if (xisinf (fy))
            render_tickmarks (zticks, z_min, z_max, xPlaneN, xPlane,
                              yPlane, yPlane,
                              signum (xPlaneN-xPlane)*fx*zticklen,
                              0., 0., 2, mirror);
          else
            render_tickmarks (zticks, z_min, z_max, xPlaneN, xPlaneN,
                              yPlane, yPlane, 0.,
                              signum (yPlane-yPlaneN)*fy*zticklen,
                              0., 2, false);
        }
      else
        {
          if (xisinf (fx))
            render_tickmarks (zticks, z_min, z_max, xPlaneN, xPlane,
                              yPlaneN, yPlane, 0.,
                              signum (yPlaneN-yPlane)*fy*zticklen,
                              0., 2, mirror);
          else
            render_tickmarks (zticks, z_min, z_max, xPlane, xPlane,
                              yPlaneN, yPlane,
                              signum (xPlane-xPlaneN)*fx*zticklen,
                              0., 0., 2, false);
        }

      // FIXME: tick texts
      if (zticklabels.numel () > 0)
        {
          int halign = 2;
          int valign = (zstate == AXE_VERT_DIR ? 1 : (zSign ? 3 : 2));

          if (xySym)
            {
              if (xisinf (fy))
                render_ticktexts (zticks, zticklabels, z_min, z_max,
                                  xPlaneN+signum (xPlaneN-xPlane)*fx*ztickoffset,
                                  yPlane, 2, halign, valign, wmax, hmax);
              else
                render_ticktexts (zticks, zticklabels, z_min, z_max, xPlaneN,
                                  yPlane+signum (yPlane-yPlaneN)*fy*ztickoffset,
                                  2, halign, valign, wmax, hmax);
            }
          else
            {
              if (xisinf (fx))
                render_ticktexts (zticks, zticklabels, z_min, z_max, xPlane,
                                  yPlaneN+signum (yPlaneN-yPlane)*fy*ztickoffset,
                                  2, halign, valign, wmax, hmax);
              else
                render_ticktexts (zticks, zticklabels, z_min, z_max,
                                  xPlane+signum (xPlane-xPlaneN)*fx*ztickoffset,
                                  yPlaneN, 2, halign, valign, wmax, hmax);
            }
        }

      // minor grid lines
      if (do_zminorgrid)
        render_grid (minorgridstyle, zmticks, z_min, z_max,
                     xPlane, xPlaneN, yPlane, yPlaneN, 2, true);

      // minor tick marks
      if (do_zminortick)
        {
          if (xySym)
            {
              if (xisinf (fy))
                render_tickmarks (zmticks, z_min, z_max, xPlaneN, xPlane,
                                  yPlane, yPlane,
                                  signum (xPlaneN-xPlane)*fx*zticklen/2,
                                  0., 0., 2, mirror);
              else
                render_tickmarks (zmticks, z_min, z_max, xPlaneN, xPlaneN,
                                  yPlane, yPlane, 0.,
                                  signum (yPlane-yPlaneN)*fy*zticklen/2,
                                  0., 2, false);
            }
          else
            {
              if (xisinf (fx))
                render_tickmarks (zmticks, z_min, z_max, xPlane, xPlane,
                                  yPlaneN, yPlane, 0.,
                                  signum (yPlaneN-yPlane)*fy*zticklen/2,
                                  0., 2, mirror);
              else
                render_tickmarks (zmticks, z_min, z_max, xPlane, xPlane,
                                  yPlaneN, yPlaneN,
                                  signum (xPlane-xPlaneN)*fx*zticklen/2,
                                  0., 0., 2, false);
            }
        }

      gh_manager::get_object (props.get_zlabel ()).set ("visible", "on");
    }
  else
    gh_manager::get_object (props.get_zlabel ()).set ("visible", "off");
}

void
opengl_renderer::draw_axes_children (const axes::properties& props)
{
  // Children

  GLboolean antialias;
  glGetBooleanv (GL_LINE_SMOOTH, &antialias);

  if (antialias == GL_TRUE)
    glEnable (GL_LINE_SMOOTH);

  Matrix children = props.get_all_children ();
  std::list<graphics_object> obj_list;
  std::list<graphics_object>::iterator it;

  // 1st pass: draw light objects

  // Start with the last element of the array of child objects to
  // display them in the oder they were added to the array.

  for (octave_idx_type i = children.numel () - 1; i >= 0; i--)
    {
      graphics_object go = gh_manager::get_object (children (i));

      if (go.get_properties ().is_visible ())
        {
          if (go.isa ("light"))
            draw (go);
          else
            obj_list.push_back (go);
        }
    }

  // 2nd pass: draw other objects (with units set to "data")

  it = obj_list.begin ();
  while (it != obj_list.end ())
    {
      graphics_object go = (*it);

      // FIXME: check whether object has "units" property and it is set
      // to "data"
      if (! go.isa ("text") || go.get ("units").string_value () == "data")
        {
          set_clipping (go.get_properties ().is_clipping ());
          draw (go);

          it = obj_list.erase (it);
        }
      else
        it++;
    }

  // 3rd pass: draw remaining objects

  glDisable (GL_DEPTH_TEST);

  for (it = obj_list.begin (); it != obj_list.end (); it++)
    {
      graphics_object go = (*it);

      set_clipping (go.get_properties ().is_clipping ());
      draw (go);
    }

  glEnable (GL_DEPTH_TEST);

  set_clipping (false);

  // FIXME: finalize rendering (transparency processing)
  // FIXME: draw zoom box, if needed
}

void
opengl_renderer::draw_axes (const axes::properties& props)
{
  static double floatmax = std::numeric_limits<float>::max ();

  double x_min = props.get_x_min ();
  double x_max = props.get_x_max ();
  double y_min = props.get_y_min ();
  double y_max = props.get_y_max ();
  double z_min = props.get_z_min ();
  double z_max = props.get_z_max ();

  if (x_max > floatmax || y_max > floatmax || z_max > floatmax
      || x_min < -floatmax || y_min < -floatmax || z_min < -floatmax)
    {
      warning ("gl-render: data values greater than float capacity.  (1) Scale data, or (2) Use gnuplot");
      return;
    }

  setup_opengl_transformation (props);

  // draw axes object

  draw_axes_planes (props);
  draw_axes_boxes (props);

  set_font (props);

  draw_axes_x_grid (props);
  draw_axes_y_grid (props);
  draw_axes_z_grid (props);

  set_linestyle ("-");

  set_clipbox (x_min, x_max, y_min, y_max, z_min, z_max);

  draw_axes_children (props);
}

void
opengl_renderer::draw_line (const line::properties& props)
{
  Matrix x = xform.xscale (props.get_xdata ().matrix_value ());
  Matrix y = xform.yscale (props.get_ydata ().matrix_value ());
  Matrix z = xform.zscale (props.get_zdata ().matrix_value ());

  bool has_z = (z.numel () > 0);
  int n = static_cast<int> (std::min (std::min (x.numel (), y.numel ()),
                                      (has_z ? z.numel ()
                                             : std::numeric_limits<int>::max ())));
  octave_uint8 clip_mask = (props.is_clipping () ? 0x7F : 0x40), clip_ok (0x40);

  std::vector<octave_uint8> clip (n);

  if (has_z)
    for (int i = 0; i < n; i++)
      clip[i] = (clip_code (x(i), y(i), z(i)) & clip_mask);
  else
    {
      double z_mid = (zmin+zmax)/2;

      for (int i = 0; i < n; i++)
        clip[i] = (clip_code (x(i), y(i), z_mid) & clip_mask);
    }

  if (! props.linestyle_is ("none"))
    {
      set_color (props.get_color_rgb ());
      set_linestyle (props.get_linestyle (), false);
      set_linewidth (props.get_linewidth ());

      if (has_z)
        {
          bool flag = false;

          for (int i = 1; i < n; i++)
            {
              if ((clip[i-1] & clip[i]) == clip_ok)
                {
                  if (! flag)
                    {
                      flag = true;
                      glBegin (GL_LINE_STRIP);
                      glVertex3d (x(i-1), y(i-1), z(i-1));
                    }
                  glVertex3d (x(i), y(i), z(i));
                }
              else if (flag)
                {
                  flag = false;
                  glEnd ();
                }
            }

          if (flag)
            glEnd ();
        }
      else
        {
          bool flag = false;

          for (int i = 1; i < n; i++)
            {
              if ((clip[i-1] & clip[i]) == clip_ok)
                {
                  if (! flag)
                    {
                      flag = true;
                      glBegin (GL_LINE_STRIP);
                      glVertex2d (x(i-1), y(i-1));
                    }
                  glVertex2d (x(i), y(i));
                }
              else if (flag)
                {
                  flag = false;
                  glEnd ();
                }
            }

          if (flag)
            glEnd ();
        }

      set_linewidth (0.5);
      set_linestyle ("-");
    }

  set_clipping (false);

  if (! props.marker_is ("none") &&
      ! (props.markeredgecolor_is ("none")
         && props.markerfacecolor_is ("none")))
    {
      Matrix lc, fc;

      if (props.markeredgecolor_is ("auto"))
        lc = props.get_color_rgb ();
      else if (! props.markeredgecolor_is ("none"))
        lc = props.get_markeredgecolor_rgb ();

      if (props.markerfacecolor_is ("auto"))
        fc = props.get_color_rgb ();
      else if (! props.markerfacecolor_is ("none"))
        fc = props.get_markerfacecolor_rgb ();

      init_marker (props.get_marker (), props.get_markersize (),
                   props.get_linewidth ());

      for (int i = 0; i < n; i++)
        {
          if (clip[i] == clip_ok)
            draw_marker (x(i), y(i),
                         has_z ? z(i) : static_cast<double> (i) / n,
                         lc, fc);
        }

      end_marker ();
    }

  set_clipping (props.is_clipping ());
}

void
opengl_renderer::draw_surface (const surface::properties& props)
{
  const Matrix x = xform.xscale (props.get_xdata ().matrix_value ());
  const Matrix y = xform.yscale (props.get_ydata ().matrix_value ());
  const Matrix z = xform.zscale (props.get_zdata ().matrix_value ());

  int zr = z.rows (), zc = z.columns ();

  NDArray c;
  const NDArray n = props.get_vertexnormals ().array_value ();

  // FIXME: handle transparency
  Matrix a;

  if (props.facelighting_is ("phong") || props.edgelighting_is ("phong"))
    warning ("opengl_renderer::draw: phong light model not supported");

  int fc_mode = (props.facecolor_is_rgb () ? 0 :
                 (props.facecolor_is ("flat") ? 1 :
                  (props.facecolor_is ("interp") ? 2 :
                   (props.facecolor_is ("texturemap") ? 3 : -1))));
  int fl_mode = (props.facelighting_is ("none") ? 0 :
                 (props.facelighting_is ("flat") ? 1 : 2));
  int fa_mode = (props.facealpha_is_double () ? 0 :
                 (props.facealpha_is ("flat") ? 1 : 2));
  int ec_mode = (props.edgecolor_is_rgb () ? 0 :
                 (props.edgecolor_is ("flat") ? 1 :
                  (props.edgecolor_is ("interp") ? 2 : -1)));
  int el_mode = (props.edgelighting_is ("none") ? 0 :
                 (props.edgelighting_is ("flat") ? 1 : 2));
  int ea_mode = (props.edgealpha_is_double () ? 0 :
                 (props.edgealpha_is ("flat") ? 1 : 2));

  Matrix fcolor = (fc_mode == 3 ? Matrix (1, 3, 1.0)
                                : props.get_facecolor_rgb ());
  Matrix ecolor = props.get_edgecolor_rgb ();

  float as = props.get_ambientstrength ();
  float ds = props.get_diffusestrength ();
  float ss = props.get_specularstrength ();
  float se = props.get_specularexponent ();
  float cb[4] = { 0.0, 0.0, 0.0, 1.0 };
  double d = 1.0;

  opengl_texture tex;

  int i1, i2, j1, j2;
  bool x_mat = (x.rows () == z.rows ());
  bool y_mat = (y.columns () == z.columns ());

  i1 = i2 = j1 = j2 = 0;

  if ((fc_mode > 0 && fc_mode < 3) || ec_mode > 0)
    c = props.get_color_data ().array_value ();

  boolMatrix clip (z.dims (), false);

  for (int i = 0; i < zr; i++)
    {
      if (x_mat)
        i1 = i;

      for (int j = 0; j < zc; j++)
        {
          if (y_mat)
            j1 = j;

          clip(i,j) = is_nan_or_inf (x(i1,j), y(i,j1), z(i,j));
        }
    }

  if (fa_mode > 0 || ea_mode > 0)
    {
      // FIXME: implement alphadata conversion
      //a = props.get_alpha_data ();
    }

  if (fl_mode > 0 || el_mode > 0)
    {
      float buf[4] = { ss, ss, ss, 1 };

      glMaterialfv (LIGHT_MODE, GL_SPECULAR, buf);
      glMaterialf (LIGHT_MODE, GL_SHININESS, se);
    }

  // FIXME: good candidate for caching, transfering pixel
  // data to OpenGL is time consuming.
  if (fc_mode == 3)
    tex = opengl_texture::create (props.get_color_data ());

  if (! props.facecolor_is ("none"))
    {
      if (props.get_facealpha_double () == 1)
        {
          if (fc_mode == 0 || fc_mode == 3)
            {
              glColor3dv (fcolor.data ());
              if (fl_mode > 0)
                {
                  for (int i = 0; i < 3; i++)
                    cb[i] = as * fcolor(i);
                  glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                  for (int i = 0; i < 3; i++)
                    cb[i] = ds * fcolor(i);
                  glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                }
            }

          if (fl_mode > 0)
            glEnable (GL_LIGHTING);
          glShadeModel ((fc_mode == 2 || fl_mode == 2) ? GL_SMOOTH : GL_FLAT);
          set_polygon_offset (true, 1);
          if (fc_mode == 3)
            glEnable (GL_TEXTURE_2D);

          for (int i = 1; i < zc; i++)
            {
              if (y_mat)
                {
                  i1 = i-1;
                  i2 = i;
                }

              for (int j = 1; j < zr; j++)
                {

                  if (clip(j-1, i-1) || clip(j, i-1)
                      || clip(j-1, i) || clip(j, i))
                    continue;

                  if (fc_mode == 1)
                    {
                      // "flat" only needs color at lower-left vertex
                      if (! xfinite (c(j-1,i-1)))
                        continue;
                    }
                  else if (fc_mode == 2)
                    {
                      // "interp" needs valid color at all 4 vertices
                      if (! (xfinite (c(j-1, i-1)) && xfinite (c(j, i-1))
                             && xfinite (c(j-1, i)) && xfinite (c(j, i))))
                        continue;
                    }

                  if (x_mat)
                    {
                      j1 = j-1;
                      j2 = j;
                    }

                  glBegin (GL_QUADS);

                  // Vertex 1
                  if (fc_mode == 3)
                    tex.tex_coord (double (i-1) / (zc-1),
                                   double (j-1) / (zr-1));
                  else if (fc_mode > 0)
                    {
                      // FIXME: is there a smarter way to do this?
                      for (int k = 0; k < 3; k++)
                        cb[k] = c(j-1, i-1, k);
                      glColor3fv (cb);

                      if (fl_mode > 0)
                        {
                          for (int k = 0; k < 3; k++)
                            cb[k] *= as;
                          glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                          for (int k = 0; k < 3; k++)
                            cb[k] = ds * c(j-1, i-1, k);
                          glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                        }
                    }
                  if (fl_mode > 0)
                    {
                      d = sqrt (n(j-1,i-1,0) * n(j-1,i-1,0)
                                + n(j-1,i-1,1) * n(j-1,i-1,1)
                                + n(j-1,i-1,2) * n(j-1,i-1,2));
                      glNormal3d (n(j-1,i-1,0)/d,
                                  n(j-1,i-1,1)/d,
                                  n(j-1,i-1,2)/d);
                    }
                  glVertex3d (x(j1,i-1), y(j-1,i1), z(j-1,i-1));

                  // Vertex 2
                  if (fc_mode == 3)
                    tex.tex_coord (double (i) / (zc-1), double (j-1) / (zr-1));
                  else if (fc_mode == 2)
                    {
                      for (int k = 0; k < 3; k++)
                        cb[k] = c(j-1, i, k);
                      glColor3fv (cb);

                      if (fl_mode > 0)
                        {
                          for (int k = 0; k < 3; k++)
                            cb[k] *= as;
                          glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                          for (int k = 0; k < 3; k++)
                            cb[k] = ds * c(j-1, i, k);
                          glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                        }
                    }

                  if (fl_mode == 2)
                    {
                      d = sqrt (n(j-1,i,0) * n(j-1,i,0)
                                + n(j-1,i,1) * n(j-1,i,1)
                                + n(j-1,i,2) * n(j-1,i,2));
                      glNormal3d (n(j-1,i,0)/d, n(j-1,i,1)/d, n(j-1,i,2)/d);
                    }

                  glVertex3d (x(j1,i), y(j-1,i2), z(j-1,i));

                  // Vertex 3
                  if (fc_mode == 3)
                    tex.tex_coord (double (i) / (zc-1), double (j) / (zr-1));
                  else if (fc_mode == 2)
                    {
                      for (int k = 0; k < 3; k++)
                        cb[k] = c(j, i, k);
                      glColor3fv (cb);

                      if (fl_mode > 0)
                        {
                          for (int k = 0; k < 3; k++)
                            cb[k] *= as;
                          glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                          for (int k = 0; k < 3; k++)
                            cb[k] = ds * c(j, i, k);
                          glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                        }
                    }
                  if (fl_mode == 2)
                    {
                      d = sqrt (n(j,i,0) * n(j,i,0)
                                + n(j,i,1) * n(j,i,1)
                                + n(j,i,2) * n(j,i,2));
                      glNormal3d (n(j,i,0)/d, n(j,i,1)/d, n(j,i,2)/d);
                    }
                  glVertex3d (x(j2,i), y(j,i2), z(j,i));

                  // Vertex 4
                  if (fc_mode == 3)
                    tex.tex_coord (double (i-1) / (zc-1), double (j) / (zr-1));
                  else if (fc_mode == 2)
                    {
                      for (int k = 0; k < 3; k++)
                        cb[k] = c(j, i-1, k);
                      glColor3fv (cb);

                      if (fl_mode > 0)
                        {
                          for (int k = 0; k < 3; k++)
                            cb[k] *= as;
                          glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                          for (int k = 0; k < 3; k++)
                            cb[k] = ds * c(j, i-1, k);
                          glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                        }
                    }
                  if (fl_mode == 2)
                    {
                      d = sqrt (n(j,i-1,0) * n(j,i-1,0)
                                + n(j,i-1,1) * n(j,i-1,1)
                                + n(j,i-1,2) * n(j,i-1,2));
                      glNormal3d (n(j,i-1,0)/d, n(j,i-1,1)/d, n(j,i-1,2)/d);
                    }
                  glVertex3d (x(j2,i-1), y(j,i1), z(j,i-1));

                  glEnd ();
                }
            }

          set_polygon_offset (false);
          if (fc_mode == 3)
            glDisable (GL_TEXTURE_2D);

          if (fl_mode > 0)
            glDisable (GL_LIGHTING);
        }
      else
        {
          // FIXME: implement transparency
        }
    }

  if (! props.edgecolor_is ("none"))
    {
      if (props.get_edgealpha_double () == 1)
        {
          if (ec_mode == 0)
            {
              glColor3dv (ecolor.data ());
              if (fl_mode > 0)
                {
                  for (int i = 0; i < 3; i++)
                    cb[i] = as * ecolor(i);
                  glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                  for (int i = 0; i < 3; i++)
                    cb[i] = ds * ecolor(i);
                  glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                }
            }

          if (el_mode > 0)
            glEnable (GL_LIGHTING);
          glShadeModel ((ec_mode == 2 || el_mode == 2) ? GL_SMOOTH : GL_FLAT);

          set_linestyle (props.get_linestyle (), false);
          set_linewidth (props.get_linewidth ());

          // Mesh along Y-axis

          if (props.meshstyle_is ("both") || props.meshstyle_is ("column"))
            {
              for (int i = 0; i < zc; i++)
                {
                  if (y_mat)
                    {
                      i1 = i-1;
                      i2 = i;
                    }

                  for (int j = 1; j < zr; j++)
                    {
                      if (clip(j-1,i) || clip(j,i))
                        continue;

                      if (ec_mode == 1)
                        {
                          // "flat" only needs color at lower-left vertex
                          if (! xfinite (c(j-1,i)))
                            continue;
                        }
                      else if (ec_mode == 2)
                        {
                          // "interp" needs valid color at both vertices
                          if (! (xfinite (c(j-1, i)) && xfinite (c(j, i))))
                            continue;
                        }

                      if (x_mat)
                        {
                          j1 = j-1;
                          j2 = j;
                        }

                      glBegin (GL_LINES);

                      // Vertex 1
                      if (ec_mode > 0)
                        {
                          for (int k = 0; k < 3; k++)
                            cb[k] = c(j-1, i, k);
                          glColor3fv (cb);

                          if (fl_mode > 0)
                            {
                              for (int k = 0; k < 3; k++)
                                cb[k] *= as;
                              glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                              for (int k = 0; k < 3; k++)
                                cb[k] = ds * c(j-1, i, k);
                              glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                            }
                        }
                      if (el_mode > 0)
                        {
                          d = sqrt (n(j-1,i,0) * n(j-1,i,0)
                                    + n(j-1,i,1) * n(j-1,i,1)
                                    + n(j-1,i,2) * n(j-1,i,2));
                          glNormal3d (n(j-1,i,0)/d, n(j-1,i,1)/d, n(j-1,i,2)/d);
                        }
                      glVertex3d (x(j1,i), y(j-1,i2), z(j-1,i));

                      // Vertex 2
                      if (ec_mode == 2)
                        {
                          for (int k = 0; k < 3; k++)
                            cb[k] = c(j, i, k);
                          glColor3fv (cb);

                          if (fl_mode > 0)
                            {
                              for (int k = 0; k < 3; k++)
                                cb[k] *= as;
                              glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                              for (int k = 0; k < 3; k++)
                                cb[k] = ds * c(j, i, k);
                              glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                            }
                        }
                      if (el_mode == 2)
                        {
                          d = sqrt (n(j,i,0) * n(j,i,0)
                                    + n(j,i,1) * n(j,i,1)
                                    + n(j,i,2) * n(j,i,2));
                          glNormal3d (n(j,i,0)/d, n(j,i,1)/d, n(j,i,2)/d);
                        }
                      glVertex3d (x(j2,i), y(j,i2), z(j,i));

                      glEnd ();
                    }
                }
            }

          // Mesh along X-axis

          if (props.meshstyle_is ("both") || props.meshstyle_is ("row"))
            {
              for (int j = 0; j < zr; j++)
                {
                  if (x_mat)
                    {
                      j1 = j-1;
                      j2 = j;
                    }

                  for (int i = 1; i < zc; i++)
                    {
                      if (clip(j,i-1) || clip(j,i))
                        continue;

                      if (ec_mode == 1)
                        {
                          // "flat" only needs color at lower-left vertex
                          if (! xfinite (c(j,i-1)))
                            continue;
                        }
                      else if (ec_mode == 2)
                        {
                          // "interp" needs valid color at both vertices
                          if (! (xfinite (c(j, i-1)) && xfinite (c(j, i))))
                            continue;
                        }

                      if (y_mat)
                        {
                          i1 = i-1;
                          i2 = i;
                        }

                      glBegin (GL_LINES);

                      // Vertex 1
                      if (ec_mode > 0)
                        {
                          for (int k = 0; k < 3; k++)
                            cb[k] = c(j, i-1, k);
                          glColor3fv (cb);

                          if (fl_mode > 0)
                            {
                              for (int k = 0; k < 3; k++)
                                cb[k] *= as;
                              glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                              for (int k = 0; k < 3; k++)
                                cb[k] = ds * c(j, i-1, k);
                              glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                            }
                        }
                      if (el_mode > 0)
                        {
                          d = sqrt (n(j,i-1,0) * n(j,i-1,0)
                                    + n(j,i-1,1) * n(j,i-1,1)
                                    + n(j,i-1,2) * n(j,i-1,2));
                          glNormal3d (n(j,i-1,0)/d, n(j,i-1,1)/d, n(j,i-1,2)/d);
                        }
                      glVertex3d (x(j2,i-1), y(j,i1), z(j,i-1));

                      // Vertex 2
                      if (ec_mode == 2)
                        {
                          for (int k = 0; k < 3; k++)
                            cb[k] = c(j, i, k);
                          glColor3fv (cb);

                          if (fl_mode > 0)
                            {
                              for (int k = 0; k < 3; k++)
                                cb[k] *= as;
                              glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                              for (int k = 0; k < 3; k++)
                                cb[k] = ds * c(j, i, k);
                              glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                            }
                        }
                      if (el_mode == 2)
                        {
                          d = sqrt (n(j,i,0) * n(j,i,0)
                                    + n(j,i,1) * n(j,i,1)
                                    + n(j,i,2) * n(j,i,2));
                          glNormal3d (n(j,i,0)/d, n(j,i,1)/d, n(j,i,2)/d);
                        }
                      glVertex3d (x(j2,i), y(j,i2), z(j,i));

                      glEnd ();
                    }
                }
            }

          set_linestyle ("-");
          set_linewidth (0.5);

          if (el_mode > 0)
            glDisable (GL_LIGHTING);
        }
      else
        {
          // FIXME: implement transparency
        }
    }

  if (! props.marker_is ("none") &&
      ! (props.markeredgecolor_is ("none")
         && props.markerfacecolor_is ("none")))
    {
      // FIXME: check how transparency should be handled in markers
      // FIXME: check what to do with marker facecolor set to auto
      //        and facecolor set to none.

      bool do_edge = ! props.markeredgecolor_is ("none");
      bool do_face = ! props.markerfacecolor_is ("none");

      Matrix mecolor = props.get_markeredgecolor_rgb ();
      Matrix mfcolor = props.get_markerfacecolor_rgb ();
      Matrix cc (1, 3, 0.0);

      if (mecolor.numel () == 0 && props.markeredgecolor_is ("auto"))
        {
          mecolor = props.get_edgecolor_rgb ();
          do_edge = ! props.edgecolor_is ("none");
        }

      if (mfcolor.numel () == 0 && props.markerfacecolor_is ("auto"))
        {
          mfcolor = props.get_facecolor_rgb ();
          do_face = ! props.facecolor_is ("none");
        }

      if ((mecolor.numel () == 0 || mfcolor.numel () == 0)
          && c.numel () == 0)
        c = props.get_color_data ().array_value ();

      init_marker (props.get_marker (), props.get_markersize (),
                   props.get_linewidth ());

      for (int i = 0; i < zc; i++)
        {
          if (y_mat)
            i1 = i;

          for (int j = 0; j < zr; j++)
            {
              if (clip(j,i))
                continue;

              if (x_mat)
                j1 = j;

              if ((do_edge && mecolor.numel () == 0)
                  || (do_face && mfcolor.numel () == 0))
                {
                  if (! xfinite (c(j,i)))
                    continue;  // Skip NaNs in color data

                  for (int k = 0; k < 3; k++)
                    cc(k) = c(j,i,k);
                }

              Matrix lc = (do_edge ? (mecolor.numel () == 0 ? cc : mecolor)
                                   : Matrix ());
              Matrix fc = (do_face ? (mfcolor.numel () == 0 ? cc : mfcolor)
                                   : Matrix ());

              draw_marker (x(j1,i), y(j,i1), z(j,i), lc, fc);
            }
        }

      end_marker ();
    }
}

// FIXME: global optimization (rendering, data structures...), there
// is probably a smarter/faster/less-memory-consuming way to do this.
void
opengl_renderer::draw_patch (const patch::properties &props)
{
  const Matrix f = props.get_faces ().matrix_value ();
  const Matrix v = xform.scale (props.get_vertices ().matrix_value ());
  Matrix c;
  const Matrix n = props.get_vertexnormals ().matrix_value ();
  Matrix a;

  int nv = v.rows ();
  // int vmax = v.columns ();
  int nf = f.rows ();
  int fcmax = f.columns ();

  bool has_z = (v.columns () > 2);
  bool has_facecolor = false;
  bool has_facealpha = false;

  int fc_mode = ((props.facecolor_is ("none")
                  || props.facecolor_is_rgb ()) ? 0 :
                 (props.facecolor_is ("flat") ? 1 : 2));
  int fl_mode = (props.facelighting_is ("none") ? 0 :
                 (props.facelighting_is ("flat") ? 1 : 2));
  int fa_mode = (props.facealpha_is_double () ? 0 :
                 (props.facealpha_is ("flat") ? 1 : 2));
  int ec_mode = ((props.edgecolor_is ("none")
                  || props.edgecolor_is_rgb ()) ? 0 :
                 (props.edgecolor_is ("flat") ? 1 : 2));
  int el_mode = (props.edgelighting_is ("none") ? 0 :
                 (props.edgelighting_is ("flat") ? 1 : 2));
  int ea_mode = (props.edgealpha_is_double () ? 0 :
                 (props.edgealpha_is ("flat") ? 1 : 2));

  Matrix fcolor = props.get_facecolor_rgb ();
  Matrix ecolor = props.get_edgecolor_rgb ();

  float as = props.get_ambientstrength ();
  float ds = props.get_diffusestrength ();
  float ss = props.get_specularstrength ();
  float se = props.get_specularexponent ();

  boolMatrix clip (1, nv, false);

  if (has_z)
    for (int i = 0; i < nv; i++)
      clip(i) = is_nan_or_inf (v(i,0), v(i,1), v(i,2));
  else
    for (int i = 0; i < nv; i++)
      clip(i) = is_nan_or_inf (v(i,0), v(i,1), 0);

  boolMatrix clip_f (1, nf, false);
  Array<int> count_f (dim_vector (nf, 1), 0);

  for (int i = 0; i < nf; i++)
    {
      bool fclip = false;
      int count = 0;

      for (int j = 0; j < fcmax && ! xisnan (f(i,j)); j++, count++)
        fclip = (fclip || clip(int (f(i,j) - 1)));

      clip_f(i) = fclip;
      count_f(i) = count;
    }

  if (fc_mode > 0 || ec_mode > 0)
    {
      c = props.get_color_data ().matrix_value ();

      if (c.rows () == 1)
        {
          // Single color specifications, we can simplify a little bit

          if (fc_mode > 0)
            {
              fcolor = c;
              fc_mode = 0;
            }

          if (ec_mode > 0)
            {
              ecolor = c;
              ec_mode = 0;
            }

          c = Matrix ();
        }
      else
        has_facecolor = ((c.numel () > 0) && (c.rows () == f.rows ()));
    }

  if (fa_mode > 0 || ea_mode > 0)
    {
      // FIXME: retrieve alpha data from patch object
      //a = props.get_alpha_data ();
      has_facealpha = ((a.numel () > 0) && (a.rows () == f.rows ()));
    }

  octave_idx_type fr = f.rows ();
  std::vector<vertex_data> vdata (f.numel ());

  for (int i = 0; i < nf; i++)
    for (int j = 0; j < count_f(i); j++)
      {
        int idx = int (f(i,j) - 1);

        Matrix vv (1, 3, 0.0);
        Matrix cc;
        Matrix nn(1, 3, 0.0);
        double aa = 1.0;

        vv(0) = v(idx,0); vv(1) = v(idx,1);
        if (has_z)
          vv(2) = v(idx,2);
        // FIXME: uncomment when patch object has normal computation
        //nn(0) = n(idx,0); nn(1) = n(idx,1); nn(2) = n(idx,2);
        if (c.numel () > 0)
          {
            cc.resize (1, 3);
            if (has_facecolor)
              cc(0) = c(i,0), cc(1) = c(i,1), cc(2) = c(i,2);
            else
              cc(0) = c(idx,0), cc(1) = c(idx,1), cc(2) = c(idx,2);
          }
        if (a.numel () > 0)
          {
            if (has_facealpha)
              aa = a(i);
            else
              aa = a(idx);
          }

        vdata[i+j*fr] =
          vertex_data (vv, cc, nn, aa, as, ds, ss, se);
      }

  if (fl_mode > 0 || el_mode > 0)
    {
      float buf[4] = { ss, ss, ss, 1 };

      glMaterialfv (LIGHT_MODE, GL_SPECULAR, buf);
      glMaterialf (LIGHT_MODE, GL_SHININESS, se);
    }

  if (! props.facecolor_is ("none"))
    {
      // FIXME: adapt to double-radio property
      if (props.get_facealpha_double () == 1)
        {
          if (fc_mode == 0)
            {
              glColor3dv (fcolor.data ());
              if (fl_mode > 0)
                {
                  float cb[4] = { 0, 0, 0, 1 };

                  for (int i = 0; i < 3; i++)
                    cb[i] = (as * fcolor(i));
                  glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                  for (int i = 0; i < 3; i++)
                    cb[i] = ds * fcolor(i);
                  glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                }
            }

          if (fl_mode > 0)
            glEnable (GL_LIGHTING);

          // FIXME: use __index__ property from patch object
          patch_tesselator tess (this, fc_mode, fl_mode, 0);

          for (int i = 0; i < nf; i++)
            {
              if (clip_f(i))
                continue;

              tess.begin_polygon (true);
              tess.begin_contour ();

              for (int j = 0; j < count_f(i); j++)
                {
                  vertex_data::vertex_data_rep *vv = vdata[i+j*fr].get_rep ();

                  tess.add_vertex (vv->coords.fortran_vec (), vv);
                }

              tess.end_contour ();
              tess.end_polygon ();
            }

          if (fl_mode > 0)
            glDisable (GL_LIGHTING);
        }
      else
        {
          // FIXME: implement transparency
        }
    }

  if (! props.edgecolor_is ("none"))
    {
      // FIXME: adapt to double-radio property
      if (props.get_edgealpha_double () == 1)
        {
          if (ec_mode == 0)
            {
              glColor3dv (ecolor.data ());
              if (el_mode > 0)
                {
                  float cb[4] = { 0, 0, 0, 1 };

                  for (int i = 0; i < 3; i++)
                    cb[i] = (as * ecolor(i));
                  glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);

                  for (int i = 0; i < 3; i++)
                    cb[i] = ds * ecolor(i);
                  glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                }
            }

          if (el_mode > 0)
            glEnable (GL_LIGHTING);

          set_linestyle (props.get_linestyle (), false);
          set_linewidth (props.get_linewidth ());


          // FIXME: use __index__ property from patch object; should we
          // offset patch contour as well?
          patch_tesselator tess (this, ec_mode, el_mode);

          for (int i = 0; i < nf; i++)
            {
              if (clip_f(i))
                {
                  // This is an unclosed contour. Draw it as a line
                  bool flag = false;

                  for (int j = 0; j < count_f(i); j++)
                    {
                      if (! clip(int (f(i,j) - 1)))
                        {
                          vertex_data::vertex_data_rep *vv
                            = vdata[i+j*fr].get_rep ();
                          const Matrix m = vv->coords;
                          if (! flag)
                            {
                              flag = true;
                              glBegin (GL_LINE_STRIP);
                            }
                          glVertex3d (m(0), m(1), m(2));
                        }
                      else if (flag)
                        {
                          flag = false;
                          glEnd ();
                        }
                    }

                  if (flag)
                    glEnd ();
                }
              else
                {
                  tess.begin_polygon (false);
                  tess.begin_contour ();

                  for (int j = 0; j < count_f(i); j++)
                    {
                      vertex_data::vertex_data_rep *vv
                        = vdata[i+j*fr].get_rep ();
                      tess.add_vertex (vv->coords.fortran_vec (), vv);
                    }

                  tess.end_contour ();
                  tess.end_polygon ();
                }
            }

          set_linestyle ("-");
          set_linewidth (0.5);

          if (el_mode > 0)
            glDisable (GL_LIGHTING);
        }
      else
        {
          // FIXME: implement transparency
        }
    }

  if (! props.marker_is ("none")
      && ! (props.markeredgecolor_is ("none")
            && props.markerfacecolor_is ("none")))
    {
      bool do_edge = ! props.markeredgecolor_is ("none");
      bool do_face = ! props.markerfacecolor_is ("none");

      Matrix mecolor = props.get_markeredgecolor_rgb ();
      Matrix mfcolor = props.get_markerfacecolor_rgb ();

      bool has_markerfacecolor = false;

      if ((mecolor.numel () == 0 && ! props.markeredgecolor_is ("none"))
          || (mfcolor.numel () == 0 && ! props.markerfacecolor_is ("none")))
        {
          Matrix mc = props.get_color_data ().matrix_value ();

          if (mc.rows () == 1)
            {
              // Single color specifications, we can simplify a little bit

              if (mfcolor.numel () == 0
                  && ! props.markerfacecolor_is ("none"))
                mfcolor = mc;

              if (mecolor.numel () == 0
                  && ! props.markeredgecolor_is ("none"))
                mecolor = mc;
            }
          else
            {
              if (c.numel () == 0)
                c = props.get_color_data ().matrix_value ();
              has_markerfacecolor = ((c.numel () > 0)
                                     && (c.rows () == f.rows ()));
            }
        }


      init_marker (props.get_marker (), props.get_markersize (),
                   props.get_linewidth ());

      for (int i = 0; i < nf; i++)
        for (int j = 0; j < count_f(i); j++)
          {
            int idx = int (f(i,j) - 1);

            if (clip(idx))
              continue;

            Matrix cc;
            if (c.numel () > 0)
              {
                cc.resize (1, 3);
                if (has_markerfacecolor)
                  cc(0) = c(i,0), cc(1) = c(i,1), cc(2) = c(i,2);
                else
                  cc(0) = c(idx,0), cc(1) = c(idx,1), cc(2) = c(idx,2);
              }

            Matrix lc = (do_edge ? (mecolor.numel () == 0 ? cc : mecolor)
                                 : Matrix ());
            Matrix fc = (do_face ? (mfcolor.numel () == 0 ? cc : mfcolor)
                                 : Matrix ());

            draw_marker (v(idx,0), v(idx,1), (has_z ? v(idx,2) : 0), lc, fc);
          }

      end_marker ();
    }
}

void
opengl_renderer::draw_hggroup (const hggroup::properties &props)
{
  draw (props.get_children ());
}

void
opengl_renderer::draw_text (const text::properties& props)
{
  if (props.get_string ().is_empty ())
    return;

  Matrix pos = xform.scale (props.get_data_position ());
  const Matrix bbox = props.get_extent_matrix ();

  // FIXME: handle margin and surrounding box
  bool blend = glIsEnabled (GL_BLEND);

  glEnable (GL_BLEND);
  glEnable (GL_ALPHA_TEST);
  glRasterPos3d (pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0);
  glBitmap (0, 0, 0, 0, bbox(0), bbox(1), 0);
  glDrawPixels (bbox(2), bbox(3),
                GL_RGBA, GL_UNSIGNED_BYTE, props.get_pixels ().data ());
  glDisable (GL_ALPHA_TEST);
  if (! blend)
    glDisable (GL_BLEND);

}

void
opengl_renderer::draw_image (const image::properties& props)
{
  octave_value cdata = props.get_color_data ();
  dim_vector dv (cdata.dims ());
  int h = dv(0), w = dv(1);

  Matrix x = props.get_xdata ().matrix_value ();
  Matrix y = props.get_ydata ().matrix_value ();

  // Someone wants us to draw an empty image? No way.
  if (x.is_empty () || y.is_empty ())
    return;

  if (w > 1 && x(1) == x(0))
    x(1) = x(1) + (w-1);

  if (h > 1 && y(1) == y(0))
    y(1) = y(1) + (h-1);

  const ColumnVector p0 = xform.transform (x(0), y(0), 0);
  const ColumnVector p1 = xform.transform (x(1), y(1), 0);

  if (xisnan (p0(0)) || xisnan (p0(1)) || xisnan (p1(0)) || xisnan (p1(1)))
    {
      warning ("gl-render: image x,y data too large to draw");
      return;
    }

  // image pixel size in screen pixel units
  float pix_dx, pix_dy;
  // image pixel size in normalized units
  float nor_dx, nor_dy;

  if (w > 1)
    {
      pix_dx = (p1(0) - p0(0))/(w-1);
      nor_dx = (x(1) - x(0))/(w-1);
    }
  else
    {
      const ColumnVector p1w = xform.transform (x(1) + 1, y(1), 0);
      pix_dx = p1w(0) - p0(0);
      nor_dx = 1;
    }

  if (h > 1)
    {
      pix_dy = (p1(1) - p0(1))/(h-1);
      nor_dy = (y(1) - y(0))/(h-1);
    }
  else
    {
      const ColumnVector p1h = xform.transform (x(1), y(1) + 1, 0);
      pix_dy = p1h(1) - p0(1);
      nor_dy = 1;
    }


  // OpenGL won't draw the image if it's origin is outside the
  // viewport/clipping plane so we must do the clipping
  // ourselfes - only draw part of the image

  int j0 = 0, j1 = w;
  int i0 = 0, i1 = h;

  float im_xmin = x(0) - nor_dx/2;
  float im_xmax = x(1) + nor_dx/2;
  float im_ymin = y(0) - nor_dy/2;
  float im_ymax = y(1) + nor_dy/2;
  if (props.is_clipping ()) // clip to axes
    {
      if (im_xmin < xmin)
        j0 += (xmin - im_xmin)/nor_dx + 1;
      if (im_xmax > xmax)
        j1 -= (im_xmax - xmax)/nor_dx ;

      if (im_ymin < ymin)
        i0 += (ymin - im_ymin)/nor_dy + 1;
      if (im_ymax > ymax)
        i1 -= (im_ymax - ymax)/nor_dy;
    }
  else // clip to viewport
    {
      GLfloat vp[4];
      glGetFloatv (GL_VIEWPORT, vp);
      // FIXME: actually add the code to do it!

    }

  if (i0 >= i1 || j0 >= j1)
    return;

  glPixelZoom (pix_dx, -pix_dy);
  glRasterPos3d (im_xmin + nor_dx*j0, im_ymin + nor_dy*i0, 0);

  // by default this is 4
  glPixelStorei (GL_UNPACK_ALIGNMENT,1);

  // Expect RGB data
  if (dv.length () == 3 && dv(2) == 3)
    {
      if (cdata.is_double_type ())
        {
          const NDArray xcdata = cdata.array_value ();

          OCTAVE_LOCAL_BUFFER (GLfloat, a, 3*(j1-j0)*(i1-i0));

          for (int i = i0; i < i1; i++)
            {
              for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
                {
                  a[idx]   = xcdata(i,j,0);
                  a[idx+1] = xcdata(i,j,1);
                  a[idx+2] = xcdata(i,j,2);
                }
            }

          draw_pixels (j1-j0, i1-i0, GL_RGB, GL_FLOAT, a);

        }
      else if (cdata.is_uint16_type ())
        {
          const uint16NDArray xcdata = cdata.uint16_array_value ();

          OCTAVE_LOCAL_BUFFER (GLushort, a, 3*(j1-j0)*(i1-i0));

          for (int i = i0; i < i1; i++)
            {
              for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
                {
                  a[idx]   = xcdata(i,j,0);
                  a[idx+1] = xcdata(i,j,1);
                  a[idx+2] = xcdata(i,j,2);
                }
            }

          draw_pixels (j1-j0, i1-i0, GL_RGB, GL_UNSIGNED_SHORT, a);

        }
      else if (cdata.is_uint8_type ())
        {
          const uint8NDArray xcdata = cdata.uint8_array_value ();

          OCTAVE_LOCAL_BUFFER (GLubyte, a, 3*(j1-j0)*(i1-i0));

          for (int i = i0; i < i1; i++)
            {
              for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
                {
                  a[idx]   = xcdata(i,j,0);
                  a[idx+1] = xcdata(i,j,1);
                  a[idx+2] = xcdata(i,j,2);
                }
            }

          draw_pixels (j1-j0, i1-i0, GL_RGB, GL_UNSIGNED_BYTE, a);
        }
      else
        warning ("opengl_texture::draw: invalid image data type (expected double, uint16, or uint8)");
    }
  else
    warning ("opengl_texture::draw: invalid image size (expected n*m*3 or n*m)");

  glPixelZoom (1, 1);
}

void
opengl_renderer::set_viewport (int w, int h)
{
  glViewport (0, 0, w, h);
}

void
opengl_renderer::draw_pixels (GLsizei width, GLsizei height, GLenum format,
                              GLenum type, const GLvoid *data)
{
  glDrawPixels (width, height, format, type, data);
}

void
opengl_renderer::set_color (const Matrix& c)
{
  glColor3dv (c.data ());
#if HAVE_FREETYPE
  text_renderer.set_color (c);
#endif
}

void
opengl_renderer::set_font (const base_properties& props)
{
#if HAVE_FREETYPE
  text_renderer.set_font (props.get ("fontname").string_value (),
                          props.get ("fontweight").string_value (),
                          props.get ("fontangle").string_value (),
                          props.get ("fontsize").double_value ());
#endif
}

void
opengl_renderer::set_polygon_offset (bool on, double offset)
{
  if (on)
    {
      glPolygonOffset (offset, offset);
      glEnable (GL_POLYGON_OFFSET_FILL);
      glEnable (GL_POLYGON_OFFSET_LINE);
    }
  else
    {
      glDisable (GL_POLYGON_OFFSET_FILL);
      glDisable (GL_POLYGON_OFFSET_LINE);
    }
}

void
opengl_renderer::set_linewidth (float w)
{
  glLineWidth (w);
}

void
opengl_renderer::set_linestyle (const std::string& s, bool use_stipple)
{
  bool solid = false;

  if (s == "-")
    {
      glLineStipple (1, static_cast<unsigned short> (0xFFFF));
      solid = true;
    }
  else if (s == ":")
    glLineStipple (1, static_cast<unsigned short> (0x8888));
  else if (s == "--")
    glLineStipple (1, static_cast<unsigned short> (0x0FFF));
  else if (s == "-.")
    glLineStipple (1, static_cast<unsigned short> (0x020F));
  else
    glLineStipple (1, static_cast<unsigned short> (0x0000));

  if (solid && ! use_stipple)
    glDisable (GL_LINE_STIPPLE);
  else
    glEnable (GL_LINE_STIPPLE);
}

void
opengl_renderer::set_clipbox (double x1, double x2, double y1, double y2,
                              double z1, double z2)
{
  double dx = (x2-x1);
  double dy = (y2-y1);
  double dz = (z2-z1);

  x1 -= 0.001*dx; x2 += 0.001*dx;
  y1 -= 0.001*dy; y2 += 0.001*dy;
  z1 -= 0.001*dz; z2 += 0.001*dz;

  ColumnVector p (4, 0.0);

  p(0) = -1; p(3) = x2;
  glClipPlane (GL_CLIP_PLANE0, p.data ());
  p(0) = 1; p(3) = -x1;
  glClipPlane (GL_CLIP_PLANE1, p.data ());
  p(0) = 0; p(1) = -1; p(3) = y2;
  glClipPlane (GL_CLIP_PLANE2, p.data ());
  p(1) = 1; p(3) = -y1;
  glClipPlane (GL_CLIP_PLANE3, p.data ());
  p(1) = 0; p(2) = -1; p(3) = z2;
  glClipPlane (GL_CLIP_PLANE4, p.data ());
  p(2) = 1; p(3) = -z1;
  glClipPlane (GL_CLIP_PLANE5, p.data ());

  xmin = x1; xmax = x2;
  ymin = y1; ymax = y2;
  zmin = z1; zmax = z2;
}

void
opengl_renderer::set_clipping (bool enable)
{
  bool has_clipping = (glIsEnabled (GL_CLIP_PLANE0) == GL_TRUE);

  if (enable != has_clipping)
    {
      if (enable)
        for (int i = 0; i < 6; i++)
          glEnable (GL_CLIP_PLANE0+i);
      else
        for (int i = 0; i < 6; i++)
          glDisable (GL_CLIP_PLANE0+i);
    }
}

void
opengl_renderer::init_marker (const std::string& m, double size, float width)
{
#if defined (HAVE_FRAMEWORK_OPENGL)
  GLint vw[4];
#else
  int vw[4];
#endif

  glGetIntegerv (GL_VIEWPORT, vw);

  glMatrixMode (GL_PROJECTION);
  glPushMatrix ();
  glLoadIdentity ();
  glOrtho (0, vw[2], vw[3], 0, xZ1, xZ2);
  glMatrixMode (GL_MODELVIEW);
  glPushMatrix ();

  set_clipping (false);
  set_linewidth (width);

  marker_id = make_marker_list (m, size, false);
  filled_marker_id = make_marker_list (m, size, true);
}

void
opengl_renderer::end_marker (void)
{
  glDeleteLists (marker_id, 1);
  glDeleteLists (filled_marker_id, 1);

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix ();
  glMatrixMode (GL_PROJECTION);
  glPopMatrix ();
  set_linewidth (0.5f);
}

void
opengl_renderer::draw_marker (double x, double y, double z,
                              const Matrix& lc, const Matrix& fc)
{
  ColumnVector tmp = xform.transform (x, y, z, false);

  glLoadIdentity ();
  glTranslated (tmp(0), tmp(1), -tmp(2));

  if (filled_marker_id > 0 && fc.numel () > 0)
    {
      glColor3dv (fc.data ());
      set_polygon_offset (true, -1.0);
      glCallList (filled_marker_id);
      if (lc.numel () > 0)
        {
          glColor3dv (lc.data ());
          glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
          glEdgeFlag (GL_TRUE);
          set_polygon_offset (true, -2.0);
          glCallList (filled_marker_id);
          glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
        }
      set_polygon_offset (false);
    }
  else if (marker_id > 0 && lc.numel () > 0)
    {
      glColor3dv (lc.data ());
      glCallList (marker_id);
    }
}

unsigned int
opengl_renderer::make_marker_list (const std::string& marker, double size,
                                   bool filled) const
{
  char c = marker[0];

  if (filled && (c == '+' || c == 'x' || c == '*' || c == '.'))
    return 0;

  unsigned int ID = glGenLists (1);
  double sz = size * toolkit.get_screen_resolution () / 72.0;

  // constants for the * marker
  const double sqrt2d4 = 0.35355339059327;
  double tt = sz*sqrt2d4;

  glNewList (ID, GL_COMPILE);

  switch (marker[0])
    {
    case '+':
      glBegin (GL_LINES);
      glVertex2f (-sz/2, 0);
      glVertex2f (sz/2, 0);
      glVertex2f (0, -sz/2);
      glVertex2f (0, sz/2);
      glEnd ();
      break;
    case 'x':
      glBegin (GL_LINES);
      glVertex2f (-sz/2, -sz/2);
      glVertex2f (sz/2, sz/2);
      glVertex2f (-sz/2, sz/2);
      glVertex2f (sz/2, -sz/2);
      glEnd ();
      break;
    case '*':
      glBegin (GL_LINES);
      glVertex2f (-sz/2, 0);
      glVertex2f (sz/2, 0);
      glVertex2f (0, -sz/2);
      glVertex2f (0, sz/2);
      glVertex2f (-tt, -tt);
      glVertex2f (+tt, +tt);
      glVertex2f (-tt, +tt);
      glVertex2f (+tt, -tt);
      glEnd ();
      break;
    case '.':
      {
        double ang_step = M_PI / 5;

        glBegin (GL_POLYGON);
        for (double ang = 0; ang < (2*M_PI); ang += ang_step)
          glVertex2d (sz*cos (ang)/3, sz*sin (ang)/3);
        glEnd ();
      }
      break;
    case 's':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2d (-sz/2, -sz/2);
      glVertex2d (-sz/2, sz/2);
      glVertex2d (sz/2, sz/2);
      glVertex2d (sz/2, -sz/2);
      glEnd ();
      break;
    case 'o':
      {
        double ang_step = M_PI / 5;

        glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
        for (double ang = 0; ang < (2*M_PI); ang += ang_step)
          glVertex2d (sz*cos (ang)/2, sz*sin (ang)/2);
        glEnd ();
      }
      break;
    case 'd':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2d (0, -sz/2);
      glVertex2d (sz/2, 0);
      glVertex2d (0, sz/2);
      glVertex2d (-sz/2, 0);
      glEnd ();
      break;
    case 'v':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2f (0, sz/2);
      glVertex2f (sz/2, -sz/2);
      glVertex2f (-sz/2, -sz/2);
      glEnd ();
      break;
    case '^':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2f (0, -sz/2);
      glVertex2f (-sz/2, sz/2);
      glVertex2f (sz/2, sz/2);
      glEnd ();
      break;
    case '>':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2f (sz/2, 0);
      glVertex2f (-sz/2, sz/2);
      glVertex2f (-sz/2, -sz/2);
      glEnd ();
      break;
    case '<':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2f (-sz/2, 0);
      glVertex2f (sz/2, -sz/2);
      glVertex2f (sz/2, sz/2);
      glEnd ();
      break;
    case 'p':
      {
        double ang;
        double r;
        double dr = 1.0 - sin (M_PI/10)/sin (3*M_PI/10)*1.02;

        glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
        for (int i = 0; i < 2*5; i++)
          {
            ang = (-0.5 + double(i+1)/5) * M_PI;
            r = 1.0 - (dr * fmod (double(i+1), 2.0));
            glVertex2d (sz*r*cos (ang)/2, sz*r*sin (ang)/2);
          }
        glEnd ();
      }
      break;
    case 'h':
      {
        double ang;
        double r;
        double dr = 1.0 - 0.5/sin (M_PI/3)*1.02;

        glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
        for (int i = 0; i < 2*6; i++)
          {
            ang = (0.5 + double(i+1)/6.0) * M_PI;
            r = 1.0 - (dr * fmod (double(i+1), 2.0));
            glVertex2d (sz*r*cos (ang)/2, sz*r*sin (ang)/2);
          }
        glEnd ();
      }
      break;
    default:
      warning ("opengl_renderer: unsupported marker '%s'",
               marker.c_str ());
      break;
    }

  glEndList ();

  return ID;
}

void
opengl_renderer::text_to_pixels (const std::string& txt,
                                 uint8NDArray& pixels,
                                 Matrix& bbox,
                                 int halign, int valign, double rotation)
{
#if HAVE_FREETYPE
  text_renderer.text_to_pixels (txt, pixels, bbox,
                                halign, valign, rotation, "none");
#endif
}

Matrix
opengl_renderer::render_text (const std::string& txt,
                              double x, double y, double z,
                              int halign, int valign, double rotation)
{
#if HAVE_FREETYPE
  if (txt.empty ())
    return Matrix (1, 4, 0.0);

  uint8NDArray pixels;
  Matrix bbox;
  text_to_pixels (txt, pixels, bbox, halign, valign, rotation);

  bool blend = glIsEnabled (GL_BLEND);

  glEnable (GL_BLEND);
  glEnable (GL_ALPHA_TEST);
  glRasterPos3d (x, y, z);
  glBitmap(0, 0, 0, 0, bbox(0), bbox(1), 0);
  glDrawPixels (bbox(2), bbox(3),
                GL_RGBA, GL_UNSIGNED_BYTE, pixels.data ());
  glDisable (GL_ALPHA_TEST);
  if (! blend)
    glDisable (GL_BLEND);

  return bbox;
#else
  ::warning ("render_text: cannot render text, Freetype library not available");
  return Matrix (1, 4, 0.0);
#endif
}

#endif
