changeset 21789:6afdf40be534

Implement graphics object "light" (patch #8943). * graphics.in.h (light): New class. * graphics.in.h (patch, surface): Make default value of facelighting property compatible with Matlab. * graphics.cc: Add function default_light_position. Export function __go_light__. * gl-render.h (opengl_renderer): Declare opengl_renderer::draw_light. New private properties "view_vector", "num_lights" and "current_light". Delete private property "has_light". * gl-render.cc (opengl_renderer::draw_light): New function. (opengl_renderer::draw): Handle drawing for light property. 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. Support lighting modes "reverselit" and "unlit". Only enable OpenGL lighting if normals are defined for patch. * scripts/plot/draw/light.m: New function. * scripts/plot/draw/module.mk: Update. * __unimplemented__.m: Remove "light" from list. * __gnuplot_draw_axes__.m: Do not throw an error if light object is present. * doc/interpreter/genpropdoc.m, doc/interpreter/module.mk, doc/interpreter/plot.txi: Document light and related properties. * NEWS: Mention new feature.
author mmuetzel <markus.muetzel@gmx.de>
date Mon, 30 May 2016 13:17:13 +0200
parents 43f613cca3ab
children f625afd1ef55
files NEWS doc/interpreter/genpropdoc.m doc/interpreter/module.mk doc/interpreter/plot.txi libinterp/corefcn/gl-render.cc libinterp/corefcn/gl-render.h libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h scripts/help/__unimplemented__.m scripts/plot/draw/light.m scripts/plot/draw/module.mk scripts/plot/util/private/__gnuplot_draw_axes__.m
diffstat 12 files changed, 846 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Sat May 28 13:33:37 2016 +0200
+++ b/NEWS	Mon May 30 13:17:13 2016 +0200
@@ -58,6 +58,9 @@
     output argument is requested, instead the vector or array of
     interpolated values is always returned for Matlab compatibility.
 
+ ** The new function "light" and corresponding graphics object provide
+    light and shadow effects for patch and surface objects.
+
  ** The surfnorm function now returns unnormalized (magnitude != 1)
     normal vectors for compatibility with Matlab.
 
--- a/doc/interpreter/genpropdoc.m	Sat May 28 13:33:37 2016 +0200
+++ b/doc/interpreter/genpropdoc.m	Mon May 30 13:17:13 2016 +0200
@@ -30,7 +30,7 @@
 
 function genpropdoc (objname, fname)
   objnames = {"root", "figure", "axes", "line", ...
-              "text", "image", "patch", "surface", ...
+              "text", "image", "patch", "surface", "light", ...
               "uimenu", "uicontextmenu", "uipanel", ...
               "uicontrol", "uitoolbar", "uipushtool", "uitoggletool"};
 
@@ -1052,10 +1052,13 @@
         s.doc = sprintf (doc_notimpl, "Transparency");
 
       case "ambientstrength"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Strength of the ambient light. Value between 0.0 and 1.0";
+        s.valid = "scalar";
 
       case "backfacelighting"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "@qcode{\"lit\"}: The normals are used as is for lighting. \
+@qcode{\"reverselit\"}: The normals are always oriented towards the point of view. \
+@qcode{\"unlit\"}: Faces with normals pointing away from the point of view are unlit.";
 
       case "cdata"
         s.valid = "matrix";
@@ -1063,7 +1066,9 @@
       case "cdatamapping"
       case "cdatasource"
       case "diffusestrength"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Strength of the diffuse reflex. Value between 0.0 (no \
+diffuse reflex) and 1.0 (full diffuse reflex).";
+        s.valid = "scalar";
 
       case "displayname"
         s.doc = "Text for the legend entry corresponding to this surface.";
@@ -1074,7 +1079,12 @@
 
       case "edgecolor"
       case "edgelighting"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "When set to a value other than @qcode{\"none\"}, the edges \
+of the object are drawn with light and shadow effects.  Supported values are \
+@qcode{\"none\"} (no lighting effects), @qcode{\"flat\"} (facetted look) and \
+@qcode{\"gouraud\"} (linear interpolation of the lighting effects between \
+the vertices). @qcode{\"phong\"} is deprecated and has the same effect as \
+@qcode{\"gouraud\"}.";
 
       case "erasemode"
         s.doc = doc_unused;
@@ -1084,7 +1094,12 @@
 
       case "facecolor"
       case "facelighting"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "When set to a value other than @qcode{\"none\"}, the faces \
+of the object are drawn with light and shadow effects.  Supported values are \
+@qcode{\"none\"} (no lighting effects), @qcode{\"flat\"} (facetted look) and \
+@qcode{\"gouraud\"} (linear interpolation of the lighting effects between \
+the vertices). @qcode{\"phong\"} is deprecated and has the same effect as \
+@qcode{\"gouraud\"}.";
 
       case "interpreter"
       case "linestyle"
@@ -1112,13 +1127,19 @@
       case "meshstyle"
       case "normalmode"
       case "specularcolorreflectance"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Reflectance for specular color. Value between 0.0 (color \
+of underlying face) and 1.0 (color of light source).";
+        s.valid = "scalar";
 
       case "specularexponent"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Exponent for the specular reflex. The lower the value, \
+the more the reflex is spread out.";
+        s.valid = "scalar";
 
       case "specularstrength"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Strength of the specular reflex. Value between 0.0 (no \
+specular reflex) and 1.0 (full specular reflex).";
+        s.valid = "scalar";
 
       case "vertexnormals"
       case "xdata"
@@ -1147,11 +1168,13 @@
         s.doc = sprintf (doc_notimpl, "Transparency");
 
       case "ambientstrength"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Strength of the ambient light. Value between 0.0 and 1.0";
         s.valid = "scalar";
 
       case "backfacelighting"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc =  "@qcode{\"lit\"}: The normals are used as is for lighting. \
+@qcode{\"reverselit\"}: The normals are always oriented towards the point of view. \
+@qcode{\"unlit\"}: Faces with normals pointing away from the point of view are unlit.";
 
       case "cdata"
         s.doc = "Data defining the patch object color.\n\
@@ -1168,7 +1191,8 @@
         s.valid = valid_scalmat;
 
       case "diffusestrength"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Strength of the diffuse reflex. Value between 0.0 (no \
+diffuse reflex) and 1.0 (full diffuse reflex).";
         s.valid = "scalar";
 
       case "displayname"
@@ -1180,7 +1204,12 @@
 
       case "edgecolor"
       case "edgelighting"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "When set to a value other than @qcode{\"none\"}, the edges \
+of the object are drawn with light and shadow effects.  Supported values are \
+@qcode{\"none\"} (no lighting effects), @qcode{\"flat\"} (facetted look) and \
+@qcode{\"gouraud\"} (linear interpolation of the lighting effects between \
+the vertices). @qcode{\"phong\"} is deprecated and has the same effect as \
+@qcode{\"gouraud\"}.";
 
       case "erasemode"
         s.doc = doc_unused;
@@ -1198,7 +1227,12 @@
                             "@qcode{\"interp\"}"});
 
       case "facelighting"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "When set to a value other than @qcode{\"none\"}, the faces \
+of the object are drawn with light and shadow effects. Supported values are \
+@qcode{\"none\"} (no lighting effects), @qcode{\"flat\"} (facetted look) and \
+@qcode{\"gouraud\"} (linear interpolation of the lighting effects between \
+the vertices). @qcode{\"phong\"} is deprecated and has the same effect as \
+@qcode{\"gouraud\"}.";
 
       case "faces"
       case "xdata"
@@ -1231,15 +1265,18 @@
 
       case "normalmode"
       case "specularcolorreflectance"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Reflectance for specular color.  Value between 0.0 (color \
+of underlying face) and 1.0 (color of light source).";
         s.valid = "scalar";
 
       case "specularexponent"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Exponent for the specular reflex.  The lower the value, \
+the more the reflex is spread out.";
         s.valid = "scalar";
 
       case "specularstrength"
-        s.doc = sprintf (doc_notimpl, "Light");
+        s.doc = "Strength of the specular reflex.  Value between 0.0 (no \
+specular reflex) and 1.0 (full specular reflex).";
         s.valid = "scalar";
 
       case "vertexnormals"
@@ -1257,6 +1294,29 @@
 
     endswitch
 
+  ## Light properties
+  elseif (strcmp (objname, "light"))
+    switch (field)
+      ## Overridden shared properties
+      case "children"
+        s.doc = doc_unused;
+
+      ## Specific properties
+      case "color"
+        s.doc = "Color of the light source.  @xref{Colors, ,colorspec}.";
+        s.valid = valid_color;
+
+      case "position"
+        s.doc = "Position of the light source.";
+
+      case "style"
+        s.doc = "This string defines whether the light emanates from a \
+light source at infinite distance (@qcode{\"infinite\"}) or from a local \
+point source (@qcode{\"local\"}). Only the default value @qcode{\"infinite\"} \
+is supported.";
+
+    endswitch
+
   ## uimenu properties
   elseif (strcmp (objname, "uimenu"))
     switch (field)
--- a/doc/interpreter/module.mk	Sat May 28 13:33:37 2016 +0200
+++ b/doc/interpreter/module.mk	Mon May 30 13:17:13 2016 +0200
@@ -4,6 +4,7 @@
   doc/interpreter/plot-axesproperties.texi \
   doc/interpreter/plot-figureproperties.texi \
   doc/interpreter/plot-imageproperties.texi \
+  doc/interpreter/plot-lightproperties.texi \
   doc/interpreter/plot-lineproperties.texi \
   doc/interpreter/plot-patchproperties.texi \
   doc/interpreter/plot-rootproperties.texi \
@@ -34,6 +35,9 @@
 doc/interpreter/plot-imageproperties.texi: doc/interpreter/genpropdoc.m
 	$(AM_V_GEN)$(call gen-propdoc-texi,image)
 
+doc/interpreter/plot-lightproperties.texi: doc/interpreter/genpropdoc.m
+	$(AM_V_GEN)$(call gen-propdoc-texi,light)
+
 doc/interpreter/plot-lineproperties.texi: doc/interpreter/genpropdoc.m
 	$(AM_V_GEN)$(call gen-propdoc-texi,line)
 
--- a/doc/interpreter/plot.txi	Sat May 28 13:33:37 2016 +0200
+++ b/doc/interpreter/plot.txi	Mon May 30 13:17:13 2016 +0200
@@ -1055,7 +1055,8 @@
 graphics handle may point to any one of a number of different base object
 types and these objects are the graphics data structures themselves.  The
 primitive graphic object types are: @code{figure}, @code{axes}, @code{line},
-@code{text}, @code{patch}, @code{surface}, @code{text}, and @code{image}.
+@code{text}, @code{patch}, @code{surface}, @code{text}, @code{image}, and
+@code{light}.
 
 Each of these objects has a function by the same name, and, each of these
 functions returns a graphics handle pointing to an object of the corresponding
@@ -1081,7 +1082,7 @@
 3. Below the @code{figure} objects are @code{axes} objects.
 
 4. Below the @code{axes} objects are @code{line}, @code{text}, @code{patch},
-@code{surface}, and @code{image} objects.
+@code{surface}, @code{image}, and @code{light} objects.
 
 Graphics handles may be distinguished from function handles
 (@pxref{Function Handles}) by means of the function @code{ishandle}.
@@ -1185,7 +1186,7 @@
 @cindex axes graphics object
 @cindex graphics object, axes
 A set of axes.  This object is a child of a figure object and may be a
-parent of line, text, image, patch, or surface objects.
+parent of line, text, image, patch, surface, or light objects.
 
 @item line
 @cindex line graphics object
@@ -1211,6 +1212,11 @@
 @cindex surface graphics object
 @cindex graphics object, surface
 A three-dimensional surface.
+
+@item light
+@cindex light graphics object
+@cindex graphics object, light
+A light object used for lighting effects on patches and surfaces.
 @c @end group
 @end table
 
@@ -1219,11 +1225,11 @@
 
 You can create any graphics object primitive by calling the function of the
 same name as the object; In other words, @code{figure}, @code{axes},
-@code{line}, @code{text}, @code{image}, @code{patch}, and @code{surface}
-functions.  These fundamental graphic objects automatically become children
-of the current axes object as if @code{hold on} was in place.  Seperately, axes
-will automatically become children of the current figure object and figures
-will become children of the root object 0.
+@code{line}, @code{text}, @code{image}, @code{patch}, @code{surface}, and
+@code{light} functions.  These fundamental graphic objects automatically become
+children of the current axes object as if @code{hold on} was in place.
+Seperately, axes will automatically become children of the current figure
+object and figures will become children of the root object 0.
 
 If this auto-joining feature is not desired then it is important to call
 @code{newplot} first to prepare a new figure and axes for plotting.
@@ -1240,6 +1246,8 @@
 
 @DOCSTRING(surface)
 
+@DOCSTRING(light)
+
 @subsubsection Handle Functions
 @cindex handle functions
 
@@ -1378,6 +1386,7 @@
 * Image Properties::
 * Patch Properties::
 * Surface Properties::
+* Light Properties::
 * Uimenu Properties::
 * Uicontextmenu Properties::
 * Uipanel Properties::
@@ -1470,6 +1479,15 @@
 
 @include plot-surfaceproperties.texi
 
+
+@node Light Properties
+@subsubsection Light Properties
+@cindex light properties
+
+The @code{light} properties are:
+
+@include plot-lightproperties.texi
+
 @node Uimenu Properties
 @subsubsection Uimenu Properties
 @cindex uimenu properties
--- a/libinterp/corefcn/gl-render.cc	Sat May 28 13:33:37 2016 +0200
+++ b/libinterp/corefcn/gl-render.cc	Mon May 30 13:17:13 2016 +0200
@@ -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"))
@@ -1598,7 +1610,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));
@@ -1607,14 +1620,33 @@
         {
           if (go.isa ("light"))
             {
-              draw (go);
-              has_light = true;
+              if (num_lights < GL_MAX_LIGHTS)
+                {
+                  current_light = GL_LIGHT0 + num_lights;
+                  set_clipping (go.get_properties ().is_clipping ());
+                  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 ();
@@ -1902,6 +1934,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 ());
@@ -1910,7 +1944,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;
 
@@ -1948,12 +1983,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.
@@ -1976,10 +2006,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);
@@ -2044,6 +2078,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)
@@ -2051,9 +2089,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));
 
@@ -2075,6 +2119,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);
                         }
                     }
 
@@ -2083,7 +2131,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));
@@ -2106,6 +2162,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)
@@ -2113,7 +2173,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));
 
@@ -2135,6 +2203,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)
@@ -2142,7 +2214,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));
 
@@ -2154,7 +2234,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
@@ -2179,10 +2259,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);
@@ -2244,6 +2328,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)
@@ -2251,7 +2339,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));
 
@@ -2271,6 +2367,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)
@@ -2278,7 +2378,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));
 
@@ -2341,6 +2449,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)
@@ -2348,7 +2460,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));
 
@@ -2368,6 +2488,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)
@@ -2375,7 +2499,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));
 
@@ -2387,7 +2519,7 @@
           set_linestyle ("-");
           set_linewidth (0.5);
 
-          if ((el_mode > 0) && has_light)
+          if ((el_mode > 0) && (num_lights > 0))
             glDisable (GL_LIGHTING);
         }
       else
@@ -2504,6 +2636,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 :
@@ -2519,6 +2653,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 ();
@@ -2526,7 +2662,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);
 
@@ -2601,8 +2738,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);
@@ -2619,16 +2766,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"))
     {
@@ -2643,16 +2785,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
@@ -2700,6 +2846,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);
                             }
                         }
                     }
@@ -2711,7 +2862,7 @@
               tess.end_polygon ();
             }
 
-          if ((fl_mode > 0) && has_light)
+          if ((fl_mode > 0) && (num_lights > 0) && has_normals)
             glDisable (GL_LIGHTING);
         }
       else
@@ -2739,10 +2890,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);
@@ -2834,7 +2989,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
@@ -2924,6 +3079,41 @@
 }
 
 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
+
+  octave_unused_parameter (props);
+
+  // 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 ());
--- a/libinterp/corefcn/gl-render.h	Sat May 28 13:33:37 2016 +0200
+++ b/libinterp/corefcn/gl-render.h	Mon May 30 13:17:13 2016 +0200
@@ -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;
--- a/libinterp/corefcn/graphics.cc	Sat May 28 13:33:37 2016 +0200
+++ b/libinterp/corefcn/graphics.cc	Mon May 30 13:17:13 2016 +0200
@@ -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"))
@@ -8090,7 +8104,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;
@@ -9521,6 +9535,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 ();
@@ -10344,6 +10359,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\
--- a/libinterp/corefcn/graphics.in.h	Sat May 28 13:33:37 2016 +0200
+++ b/libinterp/corefcn/graphics.in.h	Mon May 30 13:17:13 2016 +0200
@@ -4815,6 +4815,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:
@@ -4859,7 +4908,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 facenormals m , Matrix ()
       radio_property facenormalsmode , "{auto}|manual"
       array_property faces u , default_patch_faces ()
@@ -5062,7 +5111,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"
       array_property facenormals m , Matrix ()
       radio_property facenormalsmode , "{auto}|manual"
       // FIXME: interpreter is not a Matlab surface property
--- a/scripts/help/__unimplemented__.m	Sat May 28 13:33:37 2016 +0200
+++ b/scripts/help/__unimplemented__.m	Mon May 30 13:17:13 2016 +0200
@@ -714,7 +714,6 @@
   "libisloaded",
   "libpointer",
   "libstruct",
-  "light",
   "lightangle",
   "lighting",
   "linkdata",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/light.m	Mon May 30 13:17:13 2016 +0200
@@ -0,0 +1,415 @@
+## 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  {} {} light ()
+## @deftypefnx {} {} light (@dots{}, @var{prop}, @var{val}, @dots{})
+## @deftypefnx {} {} light (@var{hax}, @dots{})
+## @deftypefnx {} {@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 @qcode{"none"} (no lighting effects), @qcode{"flat"}
+## (facetted look of the objects) and @qcode{"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.
+## @qcode{"fltk"} and @qcode{"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. @qcode{"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 (@qcode{"infinite"}) or from a local point
+## source (@qcode{"local"}).  Only the default value @qcode{"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]);
+%! title ("FaceLighting: none - flat - gouraud");
+
+
+%!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
+
--- a/scripts/plot/draw/module.mk	Sat May 28 13:33:37 2016 +0200
+++ b/scripts/plot/draw/module.mk	Mon May 30 13:17:13 2016 +0200
@@ -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 \
--- a/scripts/plot/util/private/__gnuplot_draw_axes__.m	Sat May 28 13:33:37 2016 +0200
+++ b/scripts/plot/util/private/__gnuplot_draw_axes__.m	Mon May 30 13:17:13 2016 +0200
@@ -1337,6 +1337,9 @@
           kids = [kids; obj.children];
         endif
 
+      case "light"
+        ## ignore it
+
       otherwise
         error ("__gnuplot_draw_axes__: unknown object class, %s", obj.type);
     endswitch