changeset 7833:8ff92634982d

Add initial support for patch rendering through GLU tessellation (no transparency, no border, no markers yet). * * * Use facevertexcdata when rendering patch. * * * Add patch border rendering (no transparency yet).
author Michael Goffioul <michael.goffioul@gmail.com>
date Wed, 20 Feb 2008 16:29:37 +0100
parents e06fdf7ea647
children caab78e7e377
files src/ChangeLog src/graphics.cc src/graphics.h.in src/graphics/ChangeLog src/graphics/opengl/gl-render.cc src/graphics/opengl/gl-render.h
diffstat 6 files changed, 620 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/ChangeLog	Wed Feb 20 16:22:42 2008 +0100
+++ b/src/ChangeLog	Wed Feb 20 16:29:37 2008 +0100
@@ -15,6 +15,10 @@
 
 2008-06-04  Michael Goffioul <michael.goffioul@gmail.com>
 
+	* graphics.h.in (patch::properties::get_color_data): New utility
+	function to retrieve actual color data.
+	* graphics.cc (patch::properties::get_color_data): Likewise.
+
 	* graphics.h.in (base_scaler::is_linear, lin_scaler::is_linear,
 	scaler::is_linear): New method to detect linear scales.
 	(graphics_xform::scale(Matrix)): New method to scale 2D/3D coordinates
--- a/src/graphics.cc	Wed Feb 20 16:22:42 2008 +0100
+++ b/src/graphics.cc	Wed Feb 20 16:29:37 2008 +0100
@@ -2751,7 +2751,12 @@
 
 // ---------------------------------------------------------------------
 
-// Note: "patch" code is entirely auto-generated
+octave_value
+patch::properties::get_color_data (void) const
+{
+  return convert_cdata (*this, get_facevertexcdata (),
+			cdatamapping_is ("scaled"), 2);
+}
 
 // ---------------------------------------------------------------------
 
--- a/src/graphics.h.in	Wed Feb 20 16:22:42 2008 +0100
+++ b/src/graphics.h.in	Wed Feb 20 16:29:37 2008 +0100
@@ -2881,6 +2881,8 @@
   class OCTINTERP_API properties : public base_properties
   {
   public:
+    octave_value get_color_data (void) const;
+    
     // See the genprops.awk script for an explanation of the
     // properties declarations.
 
--- a/src/graphics/ChangeLog	Wed Feb 20 16:22:42 2008 +0100
+++ b/src/graphics/ChangeLog	Wed Feb 20 16:29:37 2008 +0100
@@ -1,3 +1,30 @@
+2008-02-20  Michael Goffioul  <michael.goffioul@gmail.com>
+
+	* opengl/gl-render.h (opengl_renderer::draw(patch)): New method to
+	render patch objects.
+	(class opengl_renderer::patch_tesselator): Forward declaration.
+	* opengl/gl-render.cc (opengl_texture::create): Use RGB data format
+	instead of RGBA.
+	(class opengl_tesselator): New classes to abstract GLU tessellation
+	process.
+	(class opengl_renderer::patch_tesselator): New class to render opaque
+	patch objects.
+	(class vertex_data): New class to hold vertex data during tessellation
+	of patch objects.
+	(opengl_renderer::draw(patch)): New method to render patch objects (no
+	transparency, no border, no marker yet).
+	(opengl_renderer::draw(graphics_object)): Dispatch to it.
+
+	* opengl/gl-render.cc (opengl_renderer::draw(patch)): Use patch color
+	data and support face/vertex single color specification.
+
+	* opengl/gl-render.cc (opengl_tesselator::begin_polygon): Set
+	tessellation property also for non-filled polygons.
+	(opengl_renderer::patch_tesselator::vertex): Protect against empty
+	color matrices.
+	(opengl_renderer::draw(patch)): Render patch border (no transparency
+	yet).
+
 2008-02-19  Michael Goffioul  <michael.goffioul@gmail.com>
 
 	* opengl/gl-render.cc (opengl_texture::texture_rep::tex_coord,
--- a/src/graphics/opengl/gl-render.cc	Wed Feb 20 16:22:42 2008 +0100
+++ b/src/graphics/opengl/gl-render.cc	Wed Feb 20 16:29:37 2008 +0100
@@ -32,6 +32,13 @@
 
 #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
+
 enum {
   AXE_ANY_DIR   = 0,
   AXE_DEPTH_DIR = 1,
@@ -39,7 +46,8 @@
   AXE_VERT_DIR  = 3
 };
 
-class opengl_texture
+class
+opengl_texture
 {
 protected:
   class texture_rep
@@ -150,37 +158,35 @@
 	{
 	  NDArray _a = data.array_value ();
 
-	  OCTAVE_LOCAL_BUFFER (float, a, (4*tw*th));
+	  OCTAVE_LOCAL_BUFFER (float, a, (3*tw*th));
 
 	  for (int i = 0; i < h; i++)
-	    for (int j = 0, idx = i*tw*4; j < w; j++, idx += 4)
+	    for (int j = 0, idx = i*tw*3; j < w; j++, idx += 3)
 	      {
 		a[idx]   = _a(i,j,0);
 		a[idx+1] = _a(i,j,1);
 		a[idx+2] = _a(i,j,2);
-		a[idx+3] = 1.0;
 	      }
 
-	  glTexImage2D (GL_TEXTURE_2D, 0, 4, tw, th, 0,
-			GL_RGBA, GL_FLOAT, a);
+	  glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0,
+			GL_RGB, GL_FLOAT, a);
 	}
       else if (data.is_uint8_type ())
 	{
 	  uint8NDArray _a = data.uint8_array_value ();
 
-	  OCTAVE_LOCAL_BUFFER (octave_uint8, a, (4*tw*th));
+	  OCTAVE_LOCAL_BUFFER (octave_uint8, a, (3*tw*th));
 
 	  for (int i = 0; i < h; i++)
-	    for (int j = 0, idx = i*tw*4; j < w; j++, idx += 4)
+	    for (int j = 0, idx = i*tw*3; j < w; j++, idx += 3)
 	      {
 		a[idx]   = _a(i,j,0);
 		a[idx+1] = _a(i,j,1);
 		a[idx+2] = _a(i,j,2);
-		a[idx+3] = octave_uint8 (0xFF);
 	      }
 
-	  glTexImage2D (GL_TEXTURE_2D, 0, 4, tw, th, 0,
-			GL_RGBA, GL_UNSIGNED_BYTE, a);
+	  glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0,
+			GL_RGB, GL_UNSIGNED_BYTE, a);
 	}
       else
 	{
@@ -205,6 +211,297 @@
   return retval;
 }
 
+class
+opengl_tesselator
+{
+public:
+  typedef void (CALLBACK *fcn) (void);
+
+public:
+
+  opengl_tesselator (void) : glu_tess (0) { 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:
+  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
+    int count;
+
+    vertex_data_rep (void) { }
+
+    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 ()) { }
+
+  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))
+    {
+      rep->count++;
+    }
+
+  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; }
+};
+
+#include <Array.cc>
+
+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) { }
+
+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)
+    {
+      vertex_data::vertex_data_rep *v0, *v1, *v2, *v3;
+      //printf("patch_tesselator::combine\n");
+
+      v0 = reinterpret_cast<vertex_data::vertex_data_rep *> (data[0]);
+      v1 = reinterpret_cast<vertex_data::vertex_data_rep *> (data[1]);
+      v2 = reinterpret_cast<vertex_data::vertex_data_rep *> (data[2]);
+      v3 = reinterpret_cast<vertex_data::vertex_data_rep *> (data[3]);
+
+      Matrix vv (1, 3, 0.0);
+      Matrix cc;
+      Matrix nn (1, 3, 0.0);
+      double aa;
+
+      vv(0) = xyz[0];
+      vv(1) = xyz[1];
+      vv(2) = xyz[2];
+      if (v0->color.numel () > 0 && v1->color.numel () > 0 &&
+	  v2->color.numel () > 0 && v3->color.numel () > 0)
+	{
+	  cc.resize (1, 3, 0.0);
+	  cc(0) = w[0]*v0->color(0)+w[1]*v1->color(0)+w[2]*v2->color(0)+w[3]*v3->color(0);
+	  cc(1) = w[0]*v0->color(1)+w[1]*v1->color(1)+w[2]*v2->color(1)+w[3]*v3->color(1);
+	  cc(2) = w[0]*v0->color(2)+w[1]*v1->color(2)+w[2]*v2->color(2)+w[3]*v3->color(2);
+	}
+      nn(0) = w[0]*v0->normal(0)+w[1]*v1->normal(0)+w[2]*v2->normal(0)+w[3]*v3->normal(0);
+      nn(1) = w[0]*v0->normal(1)+w[1]*v1->normal(1)+w[2]*v2->normal(1)+w[3]*v3->normal(1);
+      nn(2) = w[0]*v0->normal(2)+w[1]*v1->normal(2)+w[2]*v2->normal(2)+w[3]*v3->normal(2);
+      aa = w[0]*v0->alpha+w[1]*v1->alpha+w[2]*v2->alpha+w[3]*v3->alpha;
+
+      vertex_data v (vv, cc, nn, aa, v0->ambient, v0->diffuse,
+		     v0->specular, v0->specular_exp);
+      tmp_vdata.push_back (v);
+
+      *out_data = v.get_rep ();
+    }
+
+private:
+  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)
 {
@@ -221,6 +518,8 @@
     draw (dynamic_cast<const line::properties&> (props));
   else if (go.isa ("surface"))
     draw (dynamic_cast<const surface::properties&> (props));
+  else if (go.isa ("patch"))
+    draw (dynamic_cast<const patch::properties&> (props));
   else
     warning ("opengl_renderer: cannot render object of type `%s'",
 	     props.graphics_object_name ().c_str ());
@@ -1872,6 +2171,273 @@
     }
 }
 
+// FIXME: global optimization (rendering, data structures...), there
+// is probably a smarter/faster/less-memory-consuming way to do this.
+void
+opengl_renderer::draw (const patch::properties &props)
+{
+  Matrix f = props.get_faces ().matrix_value ();
+  Matrix v = xform.scale (props.get_vertices ().matrix_value ());
+  Matrix c;
+  Matrix n = props.get_vertexnormals ().matrix_value ();
+  Matrix a;
+
+  int nv = v.rows (), vmax = v.columns ();
+  int nf = f.rows (), fcmax = f.columns ();
+
+  bool has_z = (v.columns () > 2);
+  bool has_facecolor = false;
+  bool has_facealpha = false;
+
+  int fc_mode = (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));
+  // FIXME: use facealpha as to double-radio property
+  int fa_mode = 0;
+  int ec_mode = (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));
+  // FIXME: use edgealpha as to double-radio property
+  int ea_mode = 0;
+
+  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 (nf, 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 ()));
+    }
+
+  Array2<vertex_data> vdata (f.dims ());
+
+  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) =
+	    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 () == 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 / as);
+		  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).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 () == 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 / as);
+		  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))
+		continue;
+
+	      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).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")))
+    {
+      // FIXME: implement this
+    }
+}
+
 void
 opengl_renderer::set_viewport (int w, int h)
 {
--- a/src/graphics/opengl/gl-render.h	Wed Feb 20 16:22:42 2008 +0100
+++ b/src/graphics/opengl/gl-render.h	Wed Feb 20 16:29:37 2008 +0100
@@ -59,6 +59,7 @@
   virtual void draw (const axes::properties& props);
   virtual void draw (const line::properties& props);
   virtual void draw (const surface::properties& props);
+  virtual void draw (const patch::properties& props);
 
   virtual void set_color (const Matrix& c);
   virtual void set_polygon_offset (bool on, double offset = 0.0);
@@ -114,6 +115,9 @@
   unsigned int marker_id, filled_marker_id;
   /* camera information for primitive sorting */
   ColumnVector camera_pos, camera_dir;
+
+private:
+  class patch_tesselator;
 };
 
 #endif