view src/gl-render.cc @ 14237:11949c9795a0

Revamp %!demos in m-files to use Octave coding conventions on spacing, etc. Add clf() to all demos using plot features to get reproducibility. Use 64 as input to all colormaps (jet (64)) to get reproducibility. * bicubic.m, cell2mat.m, celldisp.m, cplxpair.m, interp1.m, interp2.m, interpft.m, interpn.m, profile.m, profshow.m, convhull.m, delaunay.m, griddata.m, inpolygon.m, voronoi.m, autumn.m, bone.m, contrast.m, cool.m, copper.m, flag.m, gmap40.m, gray.m, hot.m, hsv.m, image.m, imshow.m, jet.m, ocean.m, pink.m, prism.m, rainbow.m, spring.m, summer.m, white.m, winter.m, condest.m, onenormest.m, axis.m, clabel.m, colorbar.m, comet.m, comet3.m, compass.m, contour.m, contour3.m, contourf.m, cylinder.m, daspect.m, ellipsoid.m, errorbar.m, ezcontour.m, ezcontourf.m, ezmesh.m, ezmeshc.m, ezplot.m, ezplot3.m, ezpolar.m, ezsurf.m, ezsurfc.m, feather.m, fill.m, fplot.m, grid.m, hold.m, isosurface.m, legend.m, loglog.m, loglogerr.m, pareto.m, patch.m, pbaspect.m, pcolor.m, pie.m, pie3.m, plot3.m, plotmatrix.m, plotyy.m, polar.m, quiver.m, quiver3.m, rectangle.m, refreshdata.m, ribbon.m, rose.m, scatter.m, scatter3.m, semilogx.m, semilogxerr.m, semilogy.m, semilogyerr.m, shading.m, slice.m, sombrero.m, stairs.m, stem.m, stem3.m, subplot.m, surf.m, surfc.m, surfl.m, surfnorm.m, text.m, title.m, trimesh.m, triplot.m, trisurf.m, uigetdir.m, uigetfile.m, uimenu.m, uiputfile.m, waitbar.m, xlim.m, ylim.m, zlim.m, mkpp.m, pchip.m, polyaffine.m, spline.m, bicgstab.m, cgs.m, gplot.m, pcg.m, pcr.m, treeplot.m, strtok.m, demo.m, example.m, rundemos.m, speed.m, test.m, calendar.m, datestr.m, datetick.m, weekday.m: Revamp %!demos to use Octave coding conventions on spacing, etc.
author Rik <octave@nomad.inbox5.com>
date Fri, 20 Jan 2012 12:59:53 -0800
parents 72c96de7a403
children 1abb7c083171 d174210ce1ec
line wrap: on
line source

/*

Copyright (C) 2008-2012 Michael Goffioul

This file is part of Octave.

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

Octave is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

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

*/

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

#if defined (HAVE_OPENGL)

#include <iostream>

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

#define LIGHT_MODE GL_FRONT_AND_BACK

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

static octave_idx_type
xmin (octave_idx_type x, octave_idx_type y)
{
  return x < y ? x : y;
}

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 < INT_MAX)
    m <<= 1;

  return m;
}

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

  dim_vector dv (data.dims ());

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

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

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

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

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

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

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

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

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

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

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

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

  return retval;
}

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

public:

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

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

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

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

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

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

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

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

  virtual void end (void) { }

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

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

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

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

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

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

  bool is_filled (void) const { return fill; }

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

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

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

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

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

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

private:

  // No copying!

  opengl_tesselator (const opengl_tesselator&);

  opengl_tesselator operator = (const opengl_tesselator&);

  GLUtesselator *glu_tess;
  bool fill;
};

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

    // reference counter
    octave_refcount<int> count;

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

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

private:
  vertex_data_rep *rep;

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

      return nr;
    }

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

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

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

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

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

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

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

      return *this;
    }

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

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

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

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

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

      glBegin (type);
    }

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

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

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

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

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

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

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

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

      first = false;
    }

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

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

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

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

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

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

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

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

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

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

      *out_data = new_v.get_rep ();
    }

private:

  // No copying!

  patch_tesselator (const patch_tesselator&);

  patch_tesselator& operator = (const patch_tesselator&);

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

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

  const base_properties& props = go.get_properties ();

  if (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)
{
  toolkit = props.get_toolkit ();

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

  toolkit = figProps.get_toolkit ();

  // Initialize OpenGL context 

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

  // Draw children

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

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

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

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

  // Clear background

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

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

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

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

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

  glEnd ();
}

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

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

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

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

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

  Matrix x_zlim = props.get_transform_zlim ();

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

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

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

  glGetIntegerv (GL_VIEWPORT, vw);

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

  glClear (GL_DEPTH_BUFFER_BIT);

  glDisable (GL_LINE_SMOOTH);

  // store axes transformation data

  xform = props.get_transform ();
}

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

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

      glBegin (GL_QUADS);

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

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

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

      glEnd ();

      set_polygon_offset (false);
    }
}

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

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

  // Axes box

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

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

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

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

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

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

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

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

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

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

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

      glEnd ();
    }
}

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

  // X grid

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

      set_color (props.get_xcolor_rgb ());

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

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

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

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

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

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

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

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

  // Y grid

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

      set_color (props.get_ycolor_rgb ());

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

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

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

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

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

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

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

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

  // Z Grid

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

      set_color (props.get_zcolor_rgb ());

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

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

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

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

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

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

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

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

  GLboolean antialias;
  glGetBooleanv (GL_LINE_SMOOTH, &antialias);

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

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

  // 1st pass: draw light objects

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

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

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

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

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

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

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

  // 3rd pass: draw remaining objects

  glDisable (GL_DEPTH_TEST);

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

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

  glEnable (GL_DEPTH_TEST);

  set_clipping (false);

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

void
opengl_renderer::draw_axes (const axes::properties& props)
{
  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 ();

  setup_opengl_transformation (props);

  // draw axes object

  draw_axes_planes (props);
  draw_axes_boxes (props);

  set_font (props);

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

  set_linestyle ("-");

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

  draw_axes_children (props);
}

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

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

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

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

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

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

      if (has_z)
        {
          bool flag = false;

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

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

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

          if (flag)
            glEnd ();
        }

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

  set_clipping (false);

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

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

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

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

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

      end_marker ();
    }

  set_clipping (props.is_clipping ());
}

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

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

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

  // FIXME: handle transparency
  Matrix a;

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

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

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

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

  opengl_texture tex;

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

  i1 = i2 = j1 = j2 = 0;

  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 ((fc_mode > 0 && fc_mode < 3) || ec_mode > 0)
    c = props.get_color_data ().array_value ();

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

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

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

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

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

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

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

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

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

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

                  glBegin (GL_QUADS);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                  glEnd ();
                }
            }

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

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

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

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

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

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

          // Mesh along Y-axis

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

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

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

                      glBegin (GL_LINES);

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

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

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

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

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

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

                      glEnd ();
                    }
                }
            }

          // Mesh along X-axis

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

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

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

                      glBegin (GL_LINES);

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

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

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

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

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

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

                      glEnd ();
                    }
                }
            }

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

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

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

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

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

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

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

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

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

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

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

              if (x_mat)
                j1 = j;

              if ((do_edge && mecolor.numel () == 0)
                  || (do_face && mfcolor.numel () == 0))
                {
                  for (int k = 0; k < 3; k++)
                    cc(k) = c(j,i,k);
                }

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

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

      end_marker ();
    }
}

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

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

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

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

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

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

  boolMatrix clip (1, nv, false);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          if (fl_mode > 0)
            glEnable (GL_LIGHTING);

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

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

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

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

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

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

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

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

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

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

          if (el_mode > 0)
            glEnable (GL_LIGHTING);

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


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

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

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

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

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

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

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

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

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

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

      bool has_markerfacecolor = false;

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

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

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

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


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

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

            if (clip(idx))
              continue;

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

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

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

      end_marker ();
    }
}

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

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

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

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

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

}

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

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

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

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

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

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


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

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

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

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

    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  glPixelZoom (1, 1);
}

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

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

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

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

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

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

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

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

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

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

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

  ColumnVector p (4, 0.0);

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

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

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

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

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

  glGetIntegerv (GL_VIEWPORT, vw);

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

  set_clipping (false);
  set_linewidth (width);

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

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

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

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

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

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

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

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

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

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

  glNewList (ID, GL_COMPILE);

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

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

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

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

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

  glEndList ();

  return ID;
}

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

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

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

  bool blend = glIsEnabled (GL_BLEND);

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

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

#endif