view libinterp/corefcn/gl-render.cc @ 19895:19755f4fc851

maint: Cleanup C++ code to follow Octave coding conventions. Try to wrap long lines to < 80 characters. Use GNU style and don't indent first brace of function definition. "case" statement is aligned flush left with brace of switch stmt. Remove trailing '\' line continuation from the end of #define macros. Use 2 spaces for indent. * files-dock-widget.cc, history-dock-widget.cc, main-window.cc, octave-cmd.cc, octave-dock-widget.cc, octave-gui.cc, resource-manager.cc, settings-dialog.cc, shortcut-manager.cc, welcome-wizard.cc, workspace-view.cc, cellfun.cc, data.cc, debug.cc, debug.h, dirfns.cc, error.h, file-io.cc, gl-render.cc, gl-render.h, gl2ps-renderer.h, graphics.cc, graphics.in.h, help.cc, input.cc, load-path.cc, load-path.h, lookup.cc, lu.cc, oct-stream.cc, octave-default-image.h, ordschur.cc, pr-output.cc, qz.cc, strfns.cc, symtab.cc, symtab.h, sysdep.cc, variables.cc, zfstream.h, __fltk_uigetfile__.cc, __init_fltk__.cc, __magick_read__.cc, __osmesa_print__.cc, audiodevinfo.cc, ov-classdef.cc, ov-classdef.h, ov-fcn.h, ov-float.cc, ov-flt-complex.cc, ov-java.cc, ov-range.cc, ov-re-mat.cc, ov-usr-fcn.h, ov.cc, op-int.h, options-usage.h, pt-eval.cc, Array-C.cc, Array-fC.cc, Array.cc, Array.h, PermMatrix.cc, Sparse.cc, chMatrix.h, dSparse.cc, dim-vector.h, bsxfun-decl.h, bsxfun-defs.cc, oct-norm.cc, Sparse-op-defs.h, oct-inttypes.cc, oct-inttypes.h, main.in.cc, mkoctfile.in.cc: Cleanup C++ code to follow Octave coding conventions.
author Rik <rik@octave.org>
date Wed, 25 Feb 2015 11:55:49 -0800
parents 4197fc428c7d
children 17d647821d61
line wrap: on
line source

/*

Copyright (C) 2008-2015 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

// Use symbolic names for axes
enum
{
  X_AXIS,
  Y_AXIS,
  Z_AXIS
};

// Use symbolic names for color mode
enum
{
  UNIFORM,
  FLAT,
  INTERP,
  TEXTURE
};

// Use symbolic names for lighting
enum
{
  NONE,
  //FLAT,  // Already declared in anonymous enum for color mode
  GOURAUD = 2
};

// 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, w, tw, th;
      h = dv(0), w = dv(1);
      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, float idx = 0.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 == INTERP || light_mode == GOURAUD)
      glShadeModel (GL_SMOOTH);
    else
      glShadeModel (GL_FLAT);

    if (is_filled ())
      renderer->set_polygon_offset (true, 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));

    // NOTE: OpenGL can re-order vertices.  For "flat" coloring of FaceColor
    // the first vertex must be identified in the draw_patch routine.

    if (color_mode == INTERP || (color_mode == FLAT && ! is_filled ()))
      {
        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_DIFFUSE, buf);
              }
          }
      }

    if (light_mode > 0 && (first || light_mode == GOURAUD))
      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;
  int light_mode;
  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_MULTISAMPLE);
      GLint iMultiSample, iNumSamples;
      glGetIntegerv (GL_SAMPLE_BUFFERS, &iMultiSample);
      glGetIntegerv (GL_SAMPLES, &iNumSamples);
      if (iMultiSample != GL_TRUE || iNumSamples == 0)
        {
          // MultiSample not implemented.  Use old-style anti-aliasing
          glDisable (GL_MULTISAMPLE);
          glEnable (GL_LINE_SMOOTH);
          glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
        }
    }
  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 == X_AXIS)
            {
              glVertex3d (val, p1N, p2);
              glVertex3d (val, p1, p2);
              if (is_3D)
                {
                  glVertex3d (val, p1, p2N);
                  glVertex3d (val, p1, p2);
                }
            }
          else if (xyz == Y_AXIS)
            {
              glVertex3d (p1N, val, p2);
              glVertex3d (p1, val, p2);
              if (is_3D)
                {
                  glVertex3d (p1, val, p2N);
                  glVertex3d (p1, val, p2);
                }
            }
          else if (xyz == Z_AXIS)
            {
              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 == X_AXIS)
            {
              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 == Y_AXIS)
            {
              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 == Z_AXIS)
            {
              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 == X_AXIS)
            {
              b = render_text (label, val, p1, p2, ha, va);
            }
          else if (xyz == Y_AXIS)
            {
              b = render_text (label, p1, val, p2, ha, va);
            }
          else if (xyz == Z_AXIS)
            {
              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);

  // store axes transformation data

  xform = props.get_transform ();
}

void
opengl_renderer::draw_axes_planes (const axes::properties& props)
{
  Matrix axe_color = props.get_color_rgb ();
  if (axe_color.numel () == 0 || ! props.is_visible ())
    return;

  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
  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)
{
  if (! props.is_visible ())
    return;

  bool xySym = props.get_xySym ();
  bool layer2Dtop = props.get_layer2Dtop ();
  bool is2d = props.get_is2D ();
  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 ());

  glBegin (GL_LINES);

  if (layer2Dtop)
    std::swap (zpTick, zpTickN);

  // 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);
      if (! is2d)
        {
          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);

      if (! is2d)
        {
          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 ();

  if (props.is_visible () && xstate != AXE_DEPTH_DIR)
    {
      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

      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;
      int 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 ();

  if (ystate != AXE_DEPTH_DIR && props.is_visible ())
    {
      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

      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;
      int 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 ();

  if (zstate != AXE_DEPTH_DIR && props.is_visible ())
    {
      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

      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;
      int 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

  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 order 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 ("opengl_renderer: data values greater than float capacity.  (1) Scale data, or (2) Use gnuplot");
      return;
    }

  setup_opengl_transformation (props);

  // Disable line smoothing for axes
  GLboolean antialias;
  glGetBooleanv (GL_LINE_SMOOTH, &antialias);
  if (antialias == GL_TRUE)
    glDisable (GL_LINE_SMOOTH);

  // 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);

  // Re-enable line smoothing for children
  if (antialias == GL_TRUE)
    glEnable (GL_LINE_SMOOTH);

  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) : 0.0,
                         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 ();
  int 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: 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 == TEXTURE ? 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,
  //        transferring pixel data to OpenGL is time consuming.
  if (fc_mode == TEXTURE)
    tex = opengl_texture::create (props.get_color_data ());

  if (! props.facecolor_is ("none"))
    {
      if (props.get_facealpha_double () == 1)
        {
          if (fc_mode == UNIFORM || fc_mode == TEXTURE)
            {
              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 == INTERP || fl_mode == GOURAUD) ? GL_SMOOTH
                                                                  : GL_FLAT);
          set_polygon_offset (true, 1);
          if (fc_mode == TEXTURE)
            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 == FLAT)
                    {
                      // "flat" only needs color at lower-left vertex
                      if (! xfinite (c(j-1,i-1)))
                        continue;
                    }
                  else if (fc_mode == INTERP)
                    {
                      // "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 == TEXTURE)
                    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 == TEXTURE)
                    tex.tex_coord (double (i) / (zc-1), double (j-1) / (zr-1));
                  else if (fc_mode == INTERP)
                    {
                      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 == GOURAUD)
                    {
                      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 == TEXTURE)
                    tex.tex_coord (double (i) / (zc-1), double (j) / (zr-1));
                  else if (fc_mode == INTERP)
                    {
                      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 == GOURAUD)
                    {
                      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 == TEXTURE)
                    tex.tex_coord (double (i-1) / (zc-1), double (j) / (zr-1));
                  else if (fc_mode == INTERP)
                    {
                      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 == GOURAUD)
                    {
                      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 == TEXTURE)
            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 == UNIFORM)
            {
              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 == INTERP || el_mode == GOURAUD) ? 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 == FLAT)
                        {
                          // "flat" only needs color at lower-left vertex
                          if (! xfinite (c(j-1,i)))
                            continue;
                        }
                      else if (ec_mode == INTERP)
                        {
                          // "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 == INTERP)
                        {
                          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 == GOURAUD)
                        {
                          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 == FLAT)
                        {
                          // "flat" only needs color at lower-left vertex
                          if (! xfinite (c(j,i-1)))
                            continue;
                        }
                      else if (ec_mode == INTERP)
                        {
                          // "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 == INTERP)
                        {
                          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 == GOURAUD)
                        {
                          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)
{
  // Do not render if the patch has incoherent data
  std::string msg;
  if (props.has_bad_data (msg))
    {
      warning ("opengl_renderer: %s. Not rendering.", msg.c_str ());
      return;
    }

  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 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 = UNIFORM;
            }

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

          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 == UNIFORM)
            {
              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);

          // NOTE: Push filled part of patch backwards to avoid Z-fighting with
          // tesselator outline.  A value of 1.0 seems to work fine.  Value
          // can't be too large or the patch will be pushed below the axes
          // planes at +2.5.
          patch_tesselator tess (this, fc_mode, fl_mode, 1.0);

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

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

              // Add vertices in reverse order for Matlab compatibility
              for (int j = count_f(i)-1; j > 0; j--)
                {
                  vertex_data::vertex_data_rep *vv = vdata[i+j*fr].get_rep ();

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

              if (count_f(i) > 0)
                {
                  vertex_data::vertex_data_rep *vv = vdata[i].get_rep ();

                  if (fc_mode == FLAT)
                    {
                      // For "flat" shading, use color of 1st vertex.
                      Matrix col = vv->color;

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

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

                              for (int k = 0; k < 3; k++)
                                cb[k] = (vv->diffuse * col(k));
                              glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
                            }
                        }
                    }

                  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 == UNIFORM)
            {
              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 ());

          // NOTE: patch contour cannot be offset.  Offset must occur with the
          // filled portion of the patch above.  The tesselator uses
          // GLU_TESS_BOUNDARY_ONLY to get the outline of the patch and OpenGL
          // automatically sets the glType to GL_LINE_LOOP.  This primitive is
          // not supported by glPolygonOffset which is used to do Z offsets.
          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;

                  glShadeModel ((ec_mode == INTERP || el_mode == GOURAUD)
                                ? GL_SMOOTH : GL_FLAT);

                  // Add vertices in reverse order for Matlab compatibility
                  for (int j = count_f(i)-1; j >= 0; 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);
                            }
                          if (ec_mode != UNIFORM)
                            {
                              Matrix col = vv->color;

                              if (col.numel () == 3)
                                glColor3dv (col.data ());
                            }
                          glVertex3d (m(0), m(1), m(2));
                        }
                      else if (flag)
                        {
                          flag = false;
                          glEnd ();
                        }
                    }
                  // Do loop body with vertex N to "close" GL_LINE_STRIP
                  // from vertex 0 to vertex N.
                  int j = count_f(i)-1;
                  if (flag && ! clip(int (f(i,j) - 1)))
                    {
                      vertex_data::vertex_data_rep *vv
                        = vdata[i+j*fr].get_rep ();
                      const Matrix m = vv->coords;
                      if (ec_mode != UNIFORM)
                        {
                          Matrix col = vv->color;

                          if (col.numel () == 3)
                            glColor3dv (col.data ());
                        }
                      glVertex3d (m(0), m(1), m(2));
                    }

                  if (flag)
                    glEnd ();
                }
              else  // Normal edge contour drawn with tesselator
                {
                  tess.begin_polygon (false);
                  tess.begin_contour ();

                  for (int j = count_f(i)-1; j >= 0; 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;

  set_font (props);

  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);
  int 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 ("opengl_renderer: 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 any of the image if it's origin is outside the
  // viewport/clipping plane so we must do the clipping ourselves.

  int j0, j1, i0, i1;
  j0 = 0, j1 = w;
  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_single_type ())
        {
          const FloatNDArray xcdata = cdata.float_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_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 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
        warning ("opengl_renderer: invalid image data type (expected double, single, uint8, or uint16)");
    }
  else
    warning ("opengl_renderer: invalid image size (expected MxNx3 or MxN)");

  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_points").double_value ());
#endif
}

void
opengl_renderer::set_polygon_offset (bool on, float offset)
{
  if (on)
    {
      glEnable (GL_POLYGON_OFFSET_FILL);
      glEnable (GL_POLYGON_OFFSET_LINE);
      glPolygonOffset (offset, offset);
    }
  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> (0xF0F0));
  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);
      glVertex2d (-sz/2, 0);
      glVertex2d (sz/2, 0);
      glVertex2d (0, -sz/2);
      glVertex2d (0, sz/2);
      glEnd ();
      break;
    case 'x':
      glBegin (GL_LINES);
      glVertex2d (-sz/2, -sz/2);
      glVertex2d (sz/2, sz/2);
      glVertex2d (-sz/2, sz/2);
      glVertex2d (sz/2, -sz/2);
      glEnd ();
      break;
    case '*':
      glBegin (GL_LINES);
      glVertex2d (-sz/2, 0);
      glVertex2d (sz/2, 0);
      glVertex2d (0, -sz/2);
      glVertex2d (0, sz/2);
      glVertex2d (-tt, -tt);
      glVertex2d (+tt, +tt);
      glVertex2d (-tt, +tt);
      glVertex2d (+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));
      glVertex2d (0, sz/2);
      glVertex2d (sz/2, -sz/2);
      glVertex2d (-sz/2, -sz/2);
      glEnd ();
      break;
    case '^':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2d (0, -sz/2);
      glVertex2d (-sz/2, sz/2);
      glVertex2d (sz/2, sz/2);
      glEnd ();
      break;
    case '>':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2d (sz/2, 0);
      glVertex2d (-sz/2, sz/2);
      glVertex2d (-sz/2, -sz/2);
      glEnd ();
      break;
    case '<':
      glBegin ((filled ? GL_POLYGON : GL_LINE_LOOP));
      glVertex2d (-sz/2, 0);
      glVertex2d (sz/2, -sz/2);
      glVertex2d (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 ("opengl_renderer: cannot render text, FreeType library not available");
  return Matrix (1, 4, 0.0);
#endif
}

#endif