# HG changeset patch
# User mmuetzel <markus.muetzel@gmx.de>
# Date 1458747186 -3600
#      Wed Mar 23 16:33:06 2016 +0100
# Node ID 6de658263c707ed6ab6e2c4eddf6e681c2df83ff
# Parent  ffbd3e86e0be6a2ff8808d124290d8566313fbf1
implement object class "light"

* graphics.in.h: Define new class light. Change default value of
"facelighting" for surface and patch to match MATLAB.
* graphics.cc: Add function for default_light_position. Add light to
list of possible objects in make_graphics_object_from_type. Add default
properties for light to map in root_figure::init_factory_properties.
Export function __go_light__ for creation of light objects.
* gl-render.h: Declare opengl_renderer::draw_light. Add private property
view_vector for lighting mode "reverselit". Add private properties
num_lights and current_light for support of multiple light sources.
Remove private property has_light since it can be replaced by
num_lights.
* gl-render.cc: Add draw_light to opengl_renderer::draw. Add support for
up to GL_MAX_LIGHTS light sources (at least 8, limited by OpenGL). Set
view_vector before drawing anything. Set OpenGL ambient light color
before drawing other axes objects. Respect setting of
"specularcolorreflectance" on patches and surfaces. Set normals of
OpenGL faces in patch objects if possible. Face normals in surface and
patch objects towards viewer for lighting mode "reverselit". Set normals
facing away from viewer to 0 in surface and patch objects for lighting
mode "unlit". Only enable OpenGL lighting if normals are defined for
patch. Add function draw_light to place an OpenGL light source at given
position with given color (for diffuse and specular reflex).
* scripts/plot/draw/light.m: New function to add light object to axes.
Add demos resembling examples on
http://de.mathworks.com/help/matlab/visualize/reflectance-characteristics-of-graphics-objects.html
* scripts/plot/draw/module.mk: Add light.m to list of distributed
functions.
* __unimplemented__.m: Remove "light" from list.
* __gnuplot_draw_axes__.m: Do not throw an error if light object is
present.

diff -r ffbd3e86e0be -r 6de658263c70 libinterp/corefcn/gl-render.cc
--- a/libinterp/corefcn/gl-render.cc	Tue Mar 22 23:14:20 2016 -0700
+++ b/libinterp/corefcn/gl-render.cc	Wed Mar 23 16:33:06 2016 +0100
@@ -369,19 +369,22 @@
     float diffuse;
     float specular;
     float specular_exp;
+    float specular_color_refl;
 
     // reference counter
     octave_refcount<int> count;
 
     vertex_data_rep (void)
       : coords (), color (), normal (), alpha (),
-        ambient (), diffuse (), specular (), specular_exp (),count (1) { }
+        ambient (), diffuse (), specular (), specular_exp (),
+        specular_color_refl (), count (1) { }
 
     vertex_data_rep (const Matrix& c, const Matrix& col, const Matrix& n,
-                     double a, float as, float ds, float ss, float se)
+                     double a, float as, float ds, float ss, float se,
+                     float scr)
       : coords (c), color (col), normal (n), alpha (a),
         ambient (as), diffuse (ds), specular (ss), specular_exp (se),
-        count (1) { }
+        specular_color_refl (scr), count (1) { }
   };
 
 private:
@@ -402,8 +405,9 @@
   { 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))
+               double a, float as, float ds, float ss, float se,
+                     float scr)
+    : rep (new vertex_data_rep (c, col, n, a, as, ds, ss, se, scr))
   { }
 
   vertex_data (vertex_data_rep *new_rep)
@@ -490,7 +494,13 @@
                 for (int k = 0; k < 3; k++)
                   buf[k] = (v->diffuse * col(k));
                 glMaterialfv (LIGHT_MODE, GL_DIFFUSE, buf);
-              }
+
+                for (int k = 0; k < 3; k++)
+                  buf[k] = v->specular * (v->specular_color_refl +
+                           (1 - v->specular_color_refl) * col(k));
+                glMaterialfv (LIGHT_MODE, GL_SPECULAR, buf);
+
+            }
           }
       }
 
@@ -545,7 +555,7 @@
       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);
+                       v[0]->specular, v[0]->specular_exp, v[0]->specular_color_refl);
     tmp_vdata.push_back (new_v);
 
     *out_data = new_v.get_rep ();
@@ -624,6 +634,8 @@
     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 ("light"))
+    draw_light (dynamic_cast<const light::properties&> (props));
   else if (go.isa ("hggroup"))
     draw_hggroup (dynamic_cast<const hggroup::properties&> (props));
   else if (go.isa ("text"))
@@ -1512,7 +1524,8 @@
   // Start with the last element of the array of child objects to
   // display them in the order they were added to the array.
 
-  has_light = false;
+  num_lights = 0;
+
   for (octave_idx_type i = children.numel () - 1; i >= 0; i--)
     {
       graphics_object go = gh_manager::get_object (children(i));
@@ -1521,14 +1534,32 @@
         {
           if (go.isa ("light"))
             {
-              draw (go);
-              has_light = true;
+              if (num_lights < GL_MAX_LIGHTS)
+                {
+                  current_light = GL_LIGHT0 + num_lights;
+                  draw (go);
+                  num_lights++;
+                }
             }
           else
             obj_list.push_back (go);
         }
     }
 
+  // disable other OpenGL lights
+  for (int i = num_lights; i < GL_MAX_LIGHTS; i++)
+    glDisable (GL_LIGHT0 + i);
+
+  // save camera position and set ambient light color before drawing
+  // other objects
+  view_vector = props.get_cameraposition ().matrix_value ();
+
+  float cb[4] = { 1.0, 1.0, 1.0, 1.0 };
+  ColumnVector ambient_color = props.get_ambientlightcolor_rgb ();
+  for (int i = 0; i < 3; i++)
+    cb[i] = ambient_color(i);
+  glLightfv (GL_LIGHT0, GL_AMBIENT, cb);
+
   // 2nd pass: draw other objects (with units set to "data")
 
   it = obj_list.begin ();
@@ -1804,6 +1835,8 @@
                  (props.edgelighting_is ("flat") ? 1 : 2));
   int ea_mode = (props.edgealpha_is_double () ? 0 :
                  (props.edgealpha_is ("flat") ? 1 : 2));
+  int bfl_mode = (props.backfacelighting_is ("lit") ? 0 :
+                  (props.backfacelighting_is ("reverselit") ? 1 : 2));
 
   Matrix fcolor = (fc_mode == TEXTURE ? Matrix (1, 3, 1.0)
                                       : props.get_facecolor_rgb ());
@@ -1812,7 +1845,8 @@
   float as = props.get_ambientstrength ();
   float ds = props.get_diffusestrength ();
   float ss = props.get_specularstrength ();
-  float se = props.get_specularexponent ();
+  float se = props.get_specularexponent () * 5; // to fit Matlab
+  float scr = props.get_specularcolorreflectance ();
   float cb[4] = { 0.0, 0.0, 0.0, 1.0 };
   double d = 1.0;
 
@@ -1850,12 +1884,7 @@
     }
 
   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);
-    }
+    glMaterialf (LIGHT_MODE, GL_SHININESS, se);
 
   // FIXME: good candidate for caching,
   //        transferring pixel data to OpenGL is time consuming.
@@ -1878,10 +1907,14 @@
                   for (int i = 0; i < 3; i++)
                     cb[i] = ds * fcolor(i);
                   glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                  for (int i = 0; i < 3; i++)
+                    cb[i] = ss * (scr + (1-scr) * fcolor(i));
+                  glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                 }
             }
 
-          if ((fl_mode > 0) && has_light)
+          if ((fl_mode > 0) && (num_lights > 0))
             glEnable (GL_LIGHTING);
           glShadeModel ((fc_mode == INTERP || fl_mode == GOURAUD) ? GL_SMOOTH
                                                                   : GL_FLAT);
@@ -1946,6 +1979,10 @@
                           for (int k = 0; k < 3; k++)
                             cb[k] = ds * c(j-1, i-1, k);
                           glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                          for (int k = 0; k < 3; k++)
+                            cb[k] = ss * (scr + (1-scr) * c(j-1, i-1, k));
+                          glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                         }
                     }
                   if (fl_mode > 0)
@@ -1953,9 +1990,15 @@
                       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);
+                      double dir = 1.0;
+                      if (bfl_mode > 0)
+                        dir = (n(j-1,i-1,0) * view_vector(0) + 
+                               n(j-1,i-1,1) * view_vector(1) + 
+                               n(j-1,i-1,2) * view_vector(2) < 0) ? 
+                                ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+                      glNormal3d (dir * n(j-1,i-1,0)/d,
+                                  dir * n(j-1,i-1,1)/d,
+                                  dir * n(j-1,i-1,2)/d);
                     }
                   glVertex3d (x(j1,i-1), y(j-1,i1), z(j-1,i-1));
 
@@ -1977,6 +2020,10 @@
                           for (int k = 0; k < 3; k++)
                             cb[k] = ds * c(j-1, i, k);
                           glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                          for (int k = 0; k < 3; k++)
+                            cb[k] = ss * (scr + (1-scr) * c(j-1, i, k));
+                          glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                         }
                     }
 
@@ -1985,7 +2032,15 @@
                       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);
+                      double dir = 1.0;
+                      if (bfl_mode > 0)
+                        dir = (n(j-1,i,0) * view_vector(0) + 
+                               n(j-1,i,1) * view_vector(1) + 
+                               n(j-1,i,2) * view_vector(2) < 0) ? 
+                                ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+                      glNormal3d (dir * n(j-1,i,0)/d,
+                                  dir * n(j-1,i,1)/d,
+                                  dir * n(j-1,i,2)/d);
                     }
 
                   glVertex3d (x(j1,i), y(j-1,i2), z(j-1,i));
@@ -2008,6 +2063,10 @@
                           for (int k = 0; k < 3; k++)
                             cb[k] = ds * c(j, i, k);
                           glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                          for (int k = 0; k < 3; k++)
+                            cb[k] = ss * (scr + (1-scr) * c(j, i, k));
+                          glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                         }
                     }
                   if (fl_mode == GOURAUD)
@@ -2015,7 +2074,15 @@
                       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);
+                      double dir = 1.0;
+                      if (bfl_mode > 0)
+                        dir = (n(j,i,0) * view_vector(0) + 
+                               n(j,i,1) * view_vector(1) + 
+                               n(j,i,2) * view_vector(2) < 0) ? 
+                                ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+                      glNormal3d (dir * n(j,i,0)/d,
+                                  dir * n(j,i,1)/d,
+                                  dir * n(j,i,2)/d);
                     }
                   glVertex3d (x(j2,i), y(j,i2), z(j,i));
 
@@ -2037,6 +2104,10 @@
                           for (int k = 0; k < 3; k++)
                             cb[k] = ds * c(j, i-1, k);
                           glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                          for (int k = 0; k < 3; k++)
+                            cb[k] = ss * (scr + (1-scr) * c(j, i-1, k));
+                          glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                         }
                     }
                   if (fl_mode == GOURAUD)
@@ -2044,7 +2115,15 @@
                       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);
+                      double dir = 1.0;
+                      if (bfl_mode > 0)
+                        dir = (n(j,i-1,0) * view_vector(0) + 
+                               n(j,i-1,1) * view_vector(1) + 
+                               n(j,i-1,2) * view_vector(2) < 0) ? 
+                                ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+                      glNormal3d (dir * n(j,i-1,0)/d,
+                                  dir * n(j,i-1,1)/d,
+                                  dir * n(j,i-1,2)/d);
                     }
                   glVertex3d (x(j2,i-1), y(j,i1), z(j,i-1));
 
@@ -2056,7 +2135,7 @@
           if (fc_mode == TEXTURE)
             glDisable (GL_TEXTURE_2D);
 
-          if ((fl_mode > 0) && has_light)
+          if ((fl_mode > 0) && (num_lights > 0))
             glDisable (GL_LIGHTING);
         }
       else
@@ -2081,10 +2160,14 @@
                   for (int i = 0; i < 3; i++)
                     cb[i] = ds * ecolor(i);
                   glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                  for (int i = 0; i < 3; i++)
+                    cb[i] = ss * (scr + (1-scr) * ecolor(i));
+                  glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                 }
             }
 
-          if ((el_mode > 0) && has_light)
+          if ((el_mode > 0) && (num_lights > 0))
             glEnable (GL_LIGHTING);
           glShadeModel ((ec_mode == INTERP || el_mode == GOURAUD) ? GL_SMOOTH
                                                                   : GL_FLAT);
@@ -2146,6 +2229,10 @@
                               for (int k = 0; k < 3; k++)
                                 cb[k] = ds * c(j-1, i, k);
                               glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                              for (int k = 0; k < 3; k++)
+                                cb[k] = ss * (scr + (1-scr) * c(j-1, i, k));
+                              glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                             }
                         }
                       if (el_mode > 0)
@@ -2153,7 +2240,15 @@
                           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);
+                          double dir = 1.0;
+                          if (bfl_mode > 0)
+                            dir = (n(j-1,i,0) * view_vector(0) + 
+                                   n(j-1,i,1) * view_vector(1) + 
+                                   n(j-1,i,2) * view_vector(2) < 0) ? 
+                                    ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+                          glNormal3d (dir * n(j-1,i,0)/d,
+                                      dir * n(j-1,i,1)/d,
+                                      dir * n(j-1,i,2)/d);
                         }
                       glVertex3d (x(j1,i), y(j-1,i2), z(j-1,i));
 
@@ -2173,6 +2268,10 @@
                               for (int k = 0; k < 3; k++)
                                 cb[k] = ds * c(j, i, k);
                               glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                              for (int k = 0; k < 3; k++)
+                                cb[k] = ss * (scr + (1-scr) * c(j, i, k));
+                              glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                             }
                         }
                       if (el_mode == GOURAUD)
@@ -2180,7 +2279,15 @@
                           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);
+                          double dir = 1.0;
+                          if (bfl_mode > 0)
+                            dir = (n(j,i,0) * view_vector(0) + 
+                                   n(j,i,1) * view_vector(1) + 
+                                   n(j,i,2) * view_vector(2) < 0) ? 
+                                    ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+                          glNormal3d (dir * n(j,i,0)/d,
+                                      dir * n(j,i,1)/d,
+                                      dir * n(j,i,2)/d);
                         }
                       glVertex3d (x(j2,i), y(j,i2), z(j,i));
 
@@ -2243,6 +2350,10 @@
                               for (int k = 0; k < 3; k++)
                                 cb[k] = ds * c(j, i-1, k);
                               glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                              for (int k = 0; k < 3; k++)
+                                cb[k] = ss * (scr + (1-scr) * c(j, i-1, k));
+                              glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                             }
                         }
                       if (el_mode > 0)
@@ -2250,7 +2361,15 @@
                           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);
+                          double dir = 1.0;
+                          if (bfl_mode > 0)
+                            dir = (n(j,i-1,0) * view_vector(0) + 
+                                   n(j,i-1,1) * view_vector(1) + 
+                                   n(j,i-1,2) * view_vector(2) < 0) ?
+                                    ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+                          glNormal3d (dir * n(j,i-1,0)/d,
+                                      dir * n(j,i-1,1)/d,
+                                      dir * n(j,i-1,2)/d);
                         }
                       glVertex3d (x(j2,i-1), y(j,i1), z(j,i-1));
 
@@ -2270,6 +2389,10 @@
                               for (int k = 0; k < 3; k++)
                                 cb[k] = ds * c(j, i, k);
                               glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                              for (int k = 0; k < 3; k++)
+                                cb[k] = ss * (scr + (1-scr) * c(j, i, k));
+                              glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                             }
                         }
                       if (el_mode == GOURAUD)
@@ -2277,7 +2400,15 @@
                           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);
+                          double dir = 1.0;
+                          if (bfl_mode > 0)
+                            dir = (n(j,i,0) * view_vector(0) + 
+                                   n(j,i,1) * view_vector(1) + 
+                                   n(j,i,2) * view_vector(2) < 0) ? 
+                                    ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+                          glNormal3d (dir * n(j,i,0)/d,
+                                      dir * n(j,i,1)/d,
+                                      dir * n(j,i,2)/d);
                         }
                       glVertex3d (x(j2,i), y(j,i2), z(j,i));
 
@@ -2289,7 +2420,7 @@
           set_linestyle ("-");
           set_linewidth (0.5);
 
-          if ((el_mode > 0) && has_light)
+          if ((el_mode > 0) && (num_lights > 0))
             glDisable (GL_LIGHTING);
         }
       else
@@ -2402,6 +2533,8 @@
   bool has_z = (v.columns () > 2);
   bool has_facecolor = false;
   bool has_facealpha = false;
+  // FIXME: remove when patch object has normal computation (patch #8951)
+  bool has_normals = (n.rows () == nv);
 
   int fc_mode = ((props.facecolor_is ("none")
                   || props.facecolor_is_rgb ()) ? 0 :
@@ -2417,6 +2550,8 @@
                  (props.edgelighting_is ("flat") ? 1 : 2));
   int ea_mode = (props.edgealpha_is_double () ? 0 :
                  (props.edgealpha_is ("flat") ? 1 : 2));
+  int bfl_mode = (props.backfacelighting_is ("lit") ? 0 :
+                  (props.backfacelighting_is ("reverselit") ? 1 : 2));
 
   Matrix fcolor = props.get_facecolor_rgb ();
   Matrix ecolor = props.get_edgecolor_rgb ();
@@ -2424,7 +2559,8 @@
   float as = props.get_ambientstrength ();
   float ds = props.get_diffusestrength ();
   float ss = props.get_specularstrength ();
-  float se = props.get_specularexponent ();
+  float se = props.get_specularexponent () * 5; // to fit Matlab
+  float scr = props.get_specularcolorreflectance ();
 
   boolMatrix clip (1, nv, false);
 
@@ -2499,8 +2635,18 @@
         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 (has_normals)
+          {
+            double dir = 1.0;
+            if (bfl_mode > 0)
+              dir = (n(idx,0) * view_vector(0) + 
+                     n(idx,1) * view_vector(1) + 
+                     n(idx,2) * view_vector(2) < 0) ?
+                      ((bfl_mode > 1) ? 0.0 : -1.0) : 1.0;
+            nn(0) = dir * n(idx,0);
+            nn(1) = dir * n(idx,1);
+            nn(2) = dir * n(idx,2);
+          }
         if (c.numel () > 0)
           {
             cc.resize (1, 3);
@@ -2517,16 +2663,11 @@
               aa = a(idx);
           }
 
-        vdata[i+j*fr] = vertex_data (vv, cc, nn, aa, as, ds, ss, se);
+        vdata[i+j*fr] = vertex_data (vv, cc, nn, aa, as, ds, ss, se, scr);
       }
 
   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);
-    }
+    glMaterialf (LIGHT_MODE, GL_SHININESS, se);
 
   if (! props.facecolor_is ("none"))
     {
@@ -2541,16 +2682,20 @@
                   float cb[4] = { 0, 0, 0, 1 };
 
                   for (int i = 0; i < 3; i++)
-                    cb[i] = (as * fcolor(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);
+
+                  for (int i = 0; i < 3; i++)
+                    cb[i] = ss * (scr + (1-scr) * fcolor(i));
+                  glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                 }
             }
 
-          if ((fl_mode > 0) && has_light)
+          if ((fl_mode > 0) && (num_lights > 0) && has_normals)
             glEnable (GL_LIGHTING);
 
           // NOTE: Push filled part of patch backwards to avoid Z-fighting with
@@ -2598,6 +2743,11 @@
                               for (int k = 0; k < 3; k++)
                                 cb[k] = (vv->diffuse * col(k));
                               glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                              for (int k = 0; k < 3; k++)
+                                cb[k] = vv->specular * (vv->specular_color_refl
+                                    + (1-vv->specular_color_refl) * col(k));
+                              glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                             }
                         }
                     }
@@ -2609,7 +2759,7 @@
               tess.end_polygon ();
             }
 
-          if ((fl_mode > 0) && has_light)
+          if ((fl_mode > 0) && (num_lights > 0) && has_normals)
             glDisable (GL_LIGHTING);
         }
       else
@@ -2637,10 +2787,14 @@
                   for (int i = 0; i < 3; i++)
                     cb[i] = ds * ecolor(i);
                   glMaterialfv (LIGHT_MODE, GL_DIFFUSE, cb);
+
+                  for (int i = 0; i < 3; i++)
+                    cb[i] = ss * (scr + (1-scr) * ecolor(i));
+                  glMaterialfv (LIGHT_MODE, GL_SPECULAR, cb);
                 }
             }
 
-          if ((el_mode > 0) && has_light)
+          if ((el_mode > 0) && (num_lights > 0) && has_normals)
             glEnable (GL_LIGHTING);
 
           set_linestyle (props.get_linestyle (), false);
@@ -2732,7 +2886,7 @@
           set_linestyle ("-");
           set_linewidth (0.5);
 
-          if ((el_mode > 0) && has_light)
+          if ((el_mode > 0) && (num_lights > 0) && has_normals)
             glDisable (GL_LIGHTING);
         }
       else
@@ -2818,6 +2972,37 @@
 }
 
 void
+opengl_renderer::draw_light (const light::properties &props)
+{
+#if defined (HAVE_OPENGL)
+
+  // enable light source
+  glEnable (current_light);
+
+  // light position
+  float pos[4] = { 0, 0, 0, 0 }; // X,Y,Z,attenuation
+  Matrix lpos = props.get_position ().matrix_value ();
+  for (int i = 0; i < 3; i++)
+    pos[i] = lpos(i);
+  glLightfv (current_light, GL_POSITION, pos);
+
+  // light color
+  float col[4] = { 1, 1, 1, 1 }; // R,G,B,ALPHA (the latter has no meaning)
+  Matrix lcolor = props.get_color ().matrix_value ();
+  for (int i = 0; i < 3; i++)
+    col[i] = lcolor(i);
+  glLightfv (current_light, GL_DIFFUSE,  col);
+  glLightfv (current_light, GL_SPECULAR, col);
+
+#else
+  // This shouldn't happen because construction of opengl_renderer
+  // objects is supposed to be impossible if OpenGL is not available.
+
+  panic_impossible ();
+#endif
+}
+
+void
 opengl_renderer::draw_hggroup (const hggroup::properties &props)
 {
   draw (props.get_children ());
diff -r ffbd3e86e0be -r 6de658263c70 libinterp/corefcn/gl-render.h
--- a/libinterp/corefcn/gl-render.h	Tue Mar 22 23:14:20 2016 -0700
+++ b/libinterp/corefcn/gl-render.h	Wed Mar 23 16:33:06 2016 +0100
@@ -64,6 +64,7 @@
   virtual void draw_line (const line::properties& props);
   virtual void draw_surface (const surface::properties& props);
   virtual void draw_patch (const patch::properties& props);
+  virtual void draw_light (const light::properties& props);
   virtual void draw_hggroup (const hggroup::properties& props);
   virtual void draw_text (const text::properties& props);
   virtual void draw_image (const image::properties& props);
@@ -183,8 +184,8 @@
   // call lists identifiers for markers
   unsigned int marker_id, filled_marker_id;
 
-  // camera information for primitive sorting
-  ColumnVector camera_pos, camera_dir;
+  // camera information for primitive sorting and lighting
+  ColumnVector camera_pos, camera_dir, view_vector;
 
   // interpreter to be used by text_to_pixels
   caseless_str interpreter;
@@ -192,7 +193,8 @@
   text_renderer txt_renderer;
 
   // light object present and visible
-  bool has_light;
+  int num_lights;
+  unsigned int current_light;
 
 private:
   class patch_tesselator;
diff -r ffbd3e86e0be -r 6de658263c70 libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Tue Mar 22 23:14:20 2016 -0700
+++ b/libinterp/corefcn/graphics.cc	Wed Mar 23 16:33:06 2016 +0100
@@ -535,6 +535,18 @@
   return retval;
 }
 
+static Matrix
+default_light_position (void)
+{
+  Matrix m (1, 3);
+
+  m(0) = 1.0;
+  m(1) = 0.0;
+  m(2) = 1.0;
+
+  return m;
+}
+
 static double
 convert_font_size (double font_size, const caseless_str& from_units,
                    const caseless_str& to_units, double parent_height = 0)
@@ -1097,6 +1109,8 @@
     go = new text (h, p);
   else if (type.compare ("image"))
     go = new image (h, p);
+  else if (type.compare ("light"))
+    go = new light (h, p);
   else if (type.compare ("patch"))
     go = new patch (h, p);
   else if (type.compare ("surface"))
@@ -8075,7 +8089,7 @@
   if (fvc.is_undefined () || fvc.is_empty ())
     return Matrix ();
   else
-    return convert_cdata (*this, fvc,cdatamapping_is ("scaled"), 2);
+    return convert_cdata (*this, fvc, cdatamapping_is ("scaled"), 2);
 }
 
 static bool updating_patch_data = false;
@@ -9500,6 +9514,7 @@
   plist_map["image"] = image::properties::factory_defaults ();
   plist_map["patch"] = patch::properties::factory_defaults ();
   plist_map["surface"] = surface::properties::factory_defaults ();
+  plist_map["light"] = light::properties::factory_defaults ();
   plist_map["hggroup"] = hggroup::properties::factory_defaults ();
   plist_map["uimenu"] = uimenu::properties::factory_defaults ();
   plist_map["uicontrol"] = uicontrol::properties::factory_defaults ();
@@ -10325,6 +10340,15 @@
   GO_BODY (patch);
 }
 
+DEFUN (__go_light__, args, ,
+       "-*- texinfo -*-\n\
+@deftypefn {} {} __go_light__ (@var{parent})\n\
+Undocumented internal function.\n\
+@end deftypefn")
+{
+  GO_BODY (light);
+}
+
 DEFUN (__go_hggroup__, args, ,
        "-*- texinfo -*-\n\
 @deftypefn {} {} __go_hggroup__ (@var{parent})\n\
diff -r ffbd3e86e0be -r 6de658263c70 libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Tue Mar 22 23:14:20 2016 -0700
+++ b/libinterp/corefcn/graphics.in.h	Wed Mar 23 16:33:06 2016 +0100
@@ -4811,6 +4811,55 @@
 
 // ---------------------------------------------------------------------
 
+class OCTINTERP_API light : public base_graphics_object
+{
+public:
+  class OCTINTERP_API properties : public base_properties
+  {
+    // See the genprops.awk script for an explanation of the
+    // properties declarations.
+    // Programming note: Keep property list sorted if new ones are added.
+
+    BEGIN_PROPERTIES (light)
+      color_property color , color_values (1, 1, 1)
+      array_property position , default_light_position ()
+      radio_property style , "{infinite}|local"
+    END_PROPERTIES
+
+  protected:
+    void init (void)
+    {
+      position.add_constraint (dim_vector (1, 3));
+    }
+  };
+
+private:
+  properties xproperties;
+
+public:
+  light (const graphics_handle& mh, const graphics_handle& p)
+    : base_graphics_object (), xproperties (mh, p)
+  { }
+
+  ~light (void) { }
+
+  base_properties& get_properties (void) { return xproperties; }
+
+  const base_properties& get_properties (void) const { return xproperties; }
+
+  bool valid_object (void) const { return true; }
+
+  bool has_readonly_property (const caseless_str& pname) const
+  {
+    bool retval = xproperties.has_readonly_property (pname);
+    if (! retval)
+      retval = base_properties::has_readonly_property (pname);
+    return retval;
+  }
+};
+
+// ---------------------------------------------------------------------
+
 class OCTINTERP_API patch : public base_graphics_object
 {
 public:
@@ -4855,7 +4904,7 @@
       radio_property erasemode , "{normal}|background|xor|none"
       double_radio_property facealpha , double_radio_property (1.0, radio_values ("flat|interp"))
       color_property facecolor , color_property (color_values (0, 0, 0), radio_values ("none|flat|interp"))
-      radio_property facelighting , "{none}|flat|gouraud|phong"
+      radio_property facelighting , "none|{flat}|gouraud|phong"
       array_property faces u , default_patch_faces ()
       array_property facevertexalphadata , Matrix ()
       array_property facevertexcdata u , Matrix ()
@@ -5037,7 +5086,7 @@
       radio_property erasemode , "{normal}|none|xor|background"
       double_radio_property facealpha , double_radio_property (1.0, radio_values ("flat|interp|texturemap"))
       color_property facecolor , color_property (radio_values ("none|{flat}|interp|texturemap"), color_values (0, 0, 0))
-      radio_property facelighting , "{none}|flat|gouraud|phong"
+      radio_property facelighting , "none|{flat}|gouraud|phong"
       // FIXME: interpreter is not a Matlab surface property
       //        Octave uses this for legend() with the string displayname.
       radio_property interpreter , "{tex}|none|latex"
diff -r ffbd3e86e0be -r 6de658263c70 scripts/help/__unimplemented__.m
--- a/scripts/help/__unimplemented__.m	Tue Mar 22 23:14:20 2016 -0700
+++ b/scripts/help/__unimplemented__.m	Wed Mar 23 16:33:06 2016 +0100
@@ -712,7 +712,6 @@
   "libisloaded",
   "libpointer",
   "libstruct",
-  "light",
   "lightangle",
   "lighting",
   "linkdata",
diff -r ffbd3e86e0be -r 6de658263c70 scripts/plot/draw/light.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/light.m	Wed Mar 23 16:33:06 2016 +0100
@@ -0,0 +1,412 @@
+## Copyright (C) 2016 Markus Muetzel
+##
+## 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/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {} light ()
+## @deftypefnx {Function File} {} light (@dots{}, @var{prop}, @var{val}, @dots{})
+## @deftypefnx {Function File} {} light (@var{hax}, @dots{})
+## @deftypefnx {Function File} {@var{h} =} light (@dots{})
+## Create light object in the current axes or for axes @var{hax}.
+##
+## When a light object is present in an axes object and the properties
+## @qcode{"EdgeLighting"} or @qcode{"FaceLighting"} of a @command{patch} or
+## @command{surface} object are set to a value other than @qcode{"none"}, these
+## objects are drawn with light and shadow effects. Supported values for these
+## properties are "none" (no lighting effects), "flat" (facetted look of the
+## objects) and "gouraud" (linear interpolation of the lighting effects between
+## the vertices).
+## For @command{patch} objects, the normals must be set manually (property
+## @qcode{"VertexNormals"}).
+##
+## Up to eight light objects are supported per axes.
+##
+## Lighting is only supported for graphics toolkits supporting OpenGL (i.e.
+## "fltk" and "qt").
+##
+## The following properties specific to the light object can be passed with
+## their respective values:
+##
+## @table @asis
+## @item @qcode{"Color":} The color of the light object can be passed as an 
+## RGB-vector (e.g. @qcode{[1 0 0]} for red) or as a string (e.g. 'r' for red).
+## The default color is white (@qcode{[1 1 1]}).
+##
+## @item @qcode{"Position":} The direction from which the light emanates as an
+## 1x3-vector. The default direction is @qcode{[1 0 1]}.
+##
+## @item @qcode{"Style":} This string defines whether the light emanates from a
+## light source at infinite distance ("infinite") or from a local point source
+## ("local"). Only the default value "infinite" is supported.
+## @end table
+##
+## If @command{light} is called with an axes handle @var{hax}, it must be passed
+## as the first argument.
+##
+## Optionally, the handle to the light object is returned in @var{h}.
+##
+## @seealso{get, set, patch, surface}
+## @end deftypefn
+
+## Author: mmuetzel
+
+function h = light (varargin)
+
+  [hax, varargin] = __plt_get_axis_arg__ ("light", varargin{:});
+
+  if (isempty (hax))
+    hax = gca ();
+  else
+    hax = hax(1);
+  endif
+
+  htmp = __go_light__ (hax, varargin{:});
+
+  if (nargout > 0)
+    h = htmp;
+  endif
+
+endfunction
+
+
+%!demo
+%! %% Demonstrate effects of lighting
+%! clf;
+%! %% patches
+%! h_axes1 = subplot (2, 2, 1);
+%! [x,y,z] = meshgrid (-2:0.2:2, -2:0.2:2, -2:0.2:2);
+%! val = x.^2 + y.^2 + z.^2;
+%! fv1 = isosurface (x, y, z, val, 1);
+%! h_patch1 = patch (fv1, "FaceColor", "c", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! isonormals (x, y, z, val, h_patch1)
+%! fv2 = isosurface (x, y+3, z, val, 1);
+%! h_patch2 = patch (fv2, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! isonormals (x, y+3, z, val, h_patch2)
+%! axis equal; axis tight
+%! title ("Patch with lighting");
+%! view (3)
+%! h_light1 = light ();
+%!
+%! h_axes2 = subplot(2, 2, 2);
+%! patch (fv1, "FaceColor", "c", "EdgeColor", "none");
+%! patch (fv2, "FaceColor", "r", "EdgeColor", "none");
+%! axis equal; axis tight
+%! title ("Patch without lighting");
+%! view (3)
+%!
+%! %% surfaces
+%! h_axes3 = subplot(2, 2, 3);
+%! h_surf1 = surf (h_axes3, peaks, "LineStyle", "none", "FaceLighting", "Gouraud");
+%! title ("Surface with lighting");
+%! view (3)
+%! h_light2 = light ();
+%!
+%! h_axes3 = subplot(2, 2, 4);
+%! h_surf2 = surf (h_axes3, peaks, "LineStyle", "none");
+%! title ("Surface without lighting");
+%! view (3)
+
+%!demo
+%! %% Lighting modes
+%! clf;
+%! [x,y,z] = meshgrid (-.2:0.05:.2, -.2:0.05:.2, -.2:0.05:.2);
+%! val = (x.^2 + y.^2 + z.^2);
+%!
+%! h_axes1 = axes ();
+%! fv = isosurface (x, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "none");
+%! isonormals (x, y, z, val, h_patch)
+%! fv = isosurface (x+.5, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "flat");
+%! isonormals (x+.5, y, z, val, h_patch)
+%! fv = isosurface (x+1, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! isonormals (x+1, y, z, val, h_patch)
+%! axis tight
+%! axis equal
+%! view(2);
+%! light ("Position", [-1 1 1]);
+
+%!demo
+%! %% multiple lights
+%! clf;
+%! h_axes = subplot (1, 2, 1);
+%! [x,y,z] = meshgrid (-2:0.1:2, -2:0.1:2, -2:0.1:2);
+%! val = x.^2 + y.^2 + z.^2;
+%! fv = isosurface (x, y, z, val, 1);
+%! h_patch = patch (fv, "FaceColor", "w", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! isonormals (x, y, z, val, h_patch)
+%! axis equal; axis tight
+%! title ("Patch with one light");
+%! view (3)
+%! h_light = light ("Color", "g");
+%!
+%! h_axes2 = subplot (1, 2, 2);
+%! h_patch2 = patch (fv, "FaceColor", "w", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! isonormals (x, y, z, val, h_patch2)
+%! axis equal; axis tight
+%! title ("Patch with three lights");
+%! view (3)
+%! h_light1 = light ("Color", "r");
+%! h_light2 = light ("Position", [0 1 1], "Color", "b");
+%! h_light3 = light ("Position", [-1 -1 2], "Color", "g");
+
+%!demo
+%! %% Diffuse and specular reflex
+%! clf;
+%! h_axes = axes ();
+%! [x,y,z] = meshgrid (-.2:0.02:.2, -.2:0.02:.2, -.2:0.02:.2);
+%! val = (x.^2 + y.^2 + z.^2);
+%!
+%! fv = isosurface (x, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 0, "SpecularStrength", 0)
+%! isonormals (x, y, z, val, h_patch)
+%! fv = isosurface (x+.5, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 0, "SpecularStrength", .5)
+%! isonormals (x+.5, y, z, val, h_patch)
+%! fv = isosurface (x+1, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 0, "SpecularStrength", 1)
+%! isonormals (x+1, y, z, val, h_patch)
+%!
+%! fv = isosurface (x, y+.5, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 0.5, "SpecularStrength", 0)
+%! isonormals (x, y+.5, z, val, h_patch)
+%! fv = isosurface (x+.5, y+.5, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 0.5, "SpecularStrength", .5)
+%! isonormals (x+.5, y+.5, z, val, h_patch)
+%! fv = isosurface (x+1, y+.5, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 0.5, "SpecularStrength", 1)
+%! isonormals (x+1, y+.5, z, val, h_patch)
+%!
+%! fv = isosurface (x, y+1, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 1, "SpecularStrength", 0)
+%! isonormals (x, y+1, z, val, h_patch)
+%! fv = isosurface (x+.5, y+1, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 1, "SpecularStrength", .5)
+%! isonormals (x+.5, y+1, z, val, h_patch)
+%! fv = isosurface (x+1, y+1, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "DiffuseStrength", 1, "SpecularStrength", 1)
+%! isonormals (x+1, y+1, z, val, h_patch)
+%!
+%! axis equal
+%! h_light = light ("Position", [-1 1 1]);
+%! view(2);
+%!
+%! xlabel ("SpecularStrength")
+%! ylabel ("DiffuseStrength")
+
+
+%!demo
+%! %% Ambient Strength and Ambient Light Color
+%! clf;
+%! [x,y,z] = meshgrid (-.2:0.05:.2, -.2:0.05:.2, -.2:0.05:.2);
+%! val = (x.^2 + y.^2 + z.^2);
+%!
+%! h_axes1 = subplot (3,1,3);
+%! set (h_axes1, "AmbientLightColor", "g")
+%! fv = isosurface (x, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", 0)
+%! isonormals (x, y, z, val, h_patch)
+%! fv = isosurface (x+.5, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", .7)
+%! isonormals (x+.5, y, z, val, h_patch)
+%! fv = isosurface (x+1, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", 1)
+%! isonormals (x+1, y, z, val, h_patch)
+%! h_light = light ("Position", [-1 1 1]);
+%! axis tight
+%! axis equal
+%! view(2);
+%! xlabel ("AmbientStrength")
+%! ylabel ("AmbientLightColor [0 1 0]")
+%!
+%! h_axes2 = subplot (3,1,2);
+%! set (h_axes2, "AmbientLightColor", [.5 0 1])
+%! fv = isosurface (x, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", 0)
+%! isonormals (x, y, z, val, h_patch)
+%! fv = isosurface (x+.5, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", .7)
+%! isonormals (x+.5, y, z, val, h_patch)
+%! fv = isosurface (x+1, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", 1)
+%! isonormals (x+1, y, z, val, h_patch)
+%! h_light = light ("Position", [-1 1 1]);
+%! axis tight
+%! axis equal
+%! view(2);
+%! ylabel ("AmbientLightColor [.5 0 1]")
+%!
+%! h_axes3 = subplot (3,1,1);
+%! set (h_axes3, "AmbientLightColor", "w")
+%! fv = isosurface (x, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", 0)
+%! isonormals (x, y, z, val, h_patch)
+%! fv = isosurface (x+.5, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", .7)
+%! isonormals (x+.5, y, z, val, h_patch)
+%! fv = isosurface (x+1, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "AmbientStrength", 1)
+%! isonormals (x+1, y, z, val, h_patch)
+%! h_light = light ("Position", [-1 1 1]);
+%! axis tight
+%! axis equal
+%! view(2);
+%! ylabel ("AmbientLightColor [1 1 1]")
+
+%!demo
+%! %% Specular Exponent
+%! clf;
+%! [x,y,z] = meshgrid (-.2:0.01:.2, -.2:0.01:.2, -.2:0.01:.2);
+%! val = (x.^2 + y.^2 + z.^2);
+%!
+%! h_axes = axes ();
+%! fv = isosurface (x, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "SpecularExponent", 15)
+%! isonormals (x, y, z, val, h_patch)
+%! fv = isosurface (x+.5, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "SpecularExponent", 5)
+%! isonormals (x+.5, y, z, val, h_patch)
+%! fv = isosurface (x+1, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "SpecularExponent", 1)
+%! isonormals (x+1, y, z, val, h_patch)
+%! h_light = light ("Position", [-1 1 1]);
+%! axis tight
+%! axis equal
+%! view(2);
+%! xlabel ("SpecularExponent")
+
+%!demo
+%! %% SpecularColorReflectance
+%! clf;
+%! [x,y,z] = meshgrid (-.2:0.02:.2, -.2:0.02:.2, -.2:0.02:.2);
+%! val = (x.^2 + y.^2 + z.^2);
+%!
+%! h_axes = axes ();
+%! fv = isosurface (x, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "SpecularColorReflectance", 0)
+%! isonormals (x, y, z, val, h_patch)
+%! fv = isosurface (x+.5, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "SpecularColorReflectance", 0.5)
+%! isonormals (x+.5, y, z, val, h_patch)
+%! fv = isosurface (x+1, y, z, val, .039);
+%! h_patch = patch (fv, "FaceColor", "r", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! set (h_patch, "SpecularColorReflectance", 1)
+%! isonormals (x+1, y, z, val, h_patch)
+%! h_light = light ("Position", [-1 1 1]);
+%! axis tight
+%! axis equal
+%! view(2);
+%! xlabel ("SpecularColorReflectance")
+
+%!demo
+%! %% BackFaceLighting
+%! [x,y,z] = meshgrid (-.5:0.1:2, -2:0.1:2, -2:0.1:2);
+%! val = x.^2 + y.^2 + z.^2;
+%! fv = isosurface (x, y, z, val, 1);
+%! h_axes1 = subplot (1, 3, 1);
+%! h_patch = patch (fv, "FaceColor", "c", "EdgeColor", "none", "FaceLighting", "Gouraud");
+%! isonormals (x, y, z, val, h_patch)
+%! vn = get (h_patch, "VertexNormals");
+%! set (h_patch, "BackFaceLighting", "reverselit");
+%! h_light = light ();
+%! view (h_axes1, [-50 30])
+%! title ("reverselit")
+%! axis equal
+%!
+%! h_axes2 = subplot (1, 3, 2);
+%! h_patch = patch (fv, "FaceColor", "c", "EdgeColor", "none", ...
+%!           "FaceLighting", "Gouraud", "VertexNormals", vn);
+%! set (h_patch, "BackFaceLighting", "lit");
+%! h_light = light ();
+%! view (h_axes2, [-50 30])
+%! title ("lit")
+%! axis equal
+%!
+%! h_axes3 = subplot (1, 3, 3);
+%! h_patch = patch (fv, "FaceColor", "c", "EdgeColor", "none", ...
+%!           "FaceLighting", "Gouraud", "VertexNormals", vn);
+%! set (h_patch, "BackFaceLighting", "unlit");
+%! h_light = light ();
+%! view (h_axes3, [-50 30])
+%! title ("unlit")
+%! axis equal
+
+%!demo
+%! %% Colored patch
+%! clf;
+%! [x,y,z] = meshgrid (-.2:0.01:.2, -.2:0.01:.2, -.2:0.01:.2);
+%! val = (x.^2 + y.^2 + z.^2);
+%!
+%! h_axes = axes ();
+%! fv = isosurface (x, y, z, val, .039, z);
+%! h_patch = patch (fv, "FaceColor", "flat", "EdgeColor", "none", ...
+%!             "FaceLighting", "Gouraud");
+%! set (h_patch, "SpecularExponent", 15)
+%! isonormals (x, y, z, val, h_patch)
+%! h_light = light ("Position", [-1 1 1]);
+%! axis tight
+%! axis equal
+%! view (3);
+
+
+%!test
+%! hf = figure ("Visible", "off");
+%! unwind_protect
+%!   h = light ();
+%!   assert (findobj (hf, "Type", "light"), h);
+%!   assert (get (h, "Position"), [1, 0, 1]);
+%!   assert (get (h, "Color"), [1, 1, 1]);
+%!   assert (get (h, "Style"), "infinite");
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
+
+%!test
+%! hf = figure ("Visible", "off");
+%! ha = gca;
+%! unwind_protect
+%!   h = light (ha, "Position", [1 2 3], "Color", "r");
+%!   assert (get (h, "Position"), [1 2 3]);
+%!   assert (get (h, "Color"), [1 0 0]);
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
+
diff -r ffbd3e86e0be -r 6de658263c70 scripts/plot/draw/module.mk
--- a/scripts/plot/draw/module.mk	Tue Mar 22 23:14:20 2016 -0700
+++ b/scripts/plot/draw/module.mk	Wed Mar 23 16:33:06 2016 +0100
@@ -49,6 +49,7 @@
   scripts/plot/draw/isocolors.m \
   scripts/plot/draw/isonormals.m \
   scripts/plot/draw/isosurface.m \
+  scripts/plot/draw/light.m \
   scripts/plot/draw/line.m \
   scripts/plot/draw/loglogerr.m \
   scripts/plot/draw/loglog.m \
diff -r ffbd3e86e0be -r 6de658263c70 scripts/plot/util/private/__gnuplot_draw_axes__.m
--- a/scripts/plot/util/private/__gnuplot_draw_axes__.m	Tue Mar 22 23:14:20 2016 -0700
+++ b/scripts/plot/util/private/__gnuplot_draw_axes__.m	Wed Mar 23 16:33:06 2016 +0100
@@ -1359,6 +1359,9 @@
           kids = [kids; obj.children];
         endif
 
+      case "light"
+        ## ignore it
+
       otherwise
         error ("__gnuplot_draw_axes__: unknown object class, %s", obj.type);
     endswitch
