changeset 25870:49ffa78f9243

Use "facenormals" for flat lighting on surfaces (bug #54024). * graphics.in.h, graphics.cc (surface::properties::update_face_normals): New function. * gl-render.cc (draw_surface): Use face normals if lighting mode is "flat". * genpropdoc.m (surface): Document "facenormals(mode)" and "vertexnormals(mode)". * light.m: Document usage of "facenormals" and "vertexnormals". * NEWS: Announce changes.
author Markus Mützel <markus.muetzel@gmx.de>
date Sat, 08 Sep 2018 20:36:30 +0200
parents cece80ddc264
children daebd587961d
files NEWS doc/interpreter/genpropdoc.m libinterp/corefcn/gl-render.cc libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h scripts/plot/draw/light.m
diffstat 6 files changed, 167 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Sat Sep 08 17:23:57 2018 +0200
+++ b/NEWS	Sat Sep 08 20:36:30 2018 +0200
@@ -51,6 +51,12 @@
     However, if the property "IntegerHandle" has been set to "off" then
     the property will return an empty matrix ([]).
 
+ ** Patch and surface graphic objects now use the "FaceNormals" property for
+    flat lighting.
+
+ ** "FaceNormals" and "EdgeNormals" for patch and surface graphic objects are
+    now calculated automatically if necessary.
+
  ** Printing using the -dtiff output device will now create compressed
     images using lzw compression.  This change was made for Matlab
     compatibility.  To produce uncompressed images use the -dtiffn
@@ -63,6 +69,9 @@
     when using OpenGL graphics, the Qt QOFFSCREENSURFACE feature must be
     available and you must use the qt graphics toolkit.
 
+ ** It is now possible to use files and folders containing Unicode characters
+    in Windows.
+
  ** New functions added in 5.0:
 
       isfile
--- a/doc/interpreter/genpropdoc.m	Sat Sep 08 17:23:57 2018 +0200
+++ b/doc/interpreter/genpropdoc.m	Sat Sep 08 20:36:30 2018 +0200
@@ -1236,6 +1236,17 @@
 the vertices). @qcode{\"phong\"} is deprecated and has the same effect as \
 @qcode{\"gouraud\"}.";
 
+      case "facenormals"
+        s.doc = "Face normals are used for lighting the edges or faces if the \
+@code{edgelighting} or @code{facelighting} properties are set to \
+@qcode{\"flat\"}.  __modemsg__";
+
+      case "facenormalsmode"
+        s.doc = "If this property is set to @qcode{\"auto\"}, \
+@code{facenormals} are automatically calculated if the @code{edgelighting} or \
+@code{facelighting} property are set to @qcode{\"flat\"} and at least one \
+@code{light} object is present and visible in the same axes.";
+
       case "interpreter"
       case "linestyle"
         s.doc = "@xref{Line Styles}.";
@@ -1260,7 +1271,6 @@
         s.valid = "scalar";
 
       case "meshstyle"
-      case "normalmode"
       case "specularcolorreflectance"
         s.doc = "Reflectance for specular color. Value between 0.0 (color \
 of underlying face) and 1.0 (color of light source).";
@@ -1277,6 +1287,16 @@
         s.valid = "scalar";
 
       case "vertexnormals"
+        s.doc = "Vertex normals are used for lighting the edges or faces if \
+the @code{edgelighting} or @code{facelighting} properties are set to \
+@qcode{\"gouraud\"}.  __modemsg__";
+
+      case "vertexnormalsmode"
+        s.doc = "If this property is set to @qcode{\"auto\"}, \
+@code{vertexnormals} are automatically calculated if the @code{edgelighting} \
+or @code{facelighting} property are set to @qcode{\"gouraud\"} and at least \
+one @code{light} object is present and visible in the same axes.";
+
       case "xdata"
         s.valid = "matrix";
 
--- a/libinterp/corefcn/gl-render.cc	Sat Sep 08 17:23:57 2018 +0200
+++ b/libinterp/corefcn/gl-render.cc	Sat Sep 08 20:36:30 2018 +0200
@@ -2391,7 +2391,8 @@
     int zc = z.columns ();
 
     NDArray c;
-    const NDArray n = props.get_vertexnormals ().array_value ();
+    const NDArray vn = props.get_vertexnormals ().array_value ();
+    const NDArray fn = props.get_facenormals ().array_value ();
 
     // FIXME: handle transparency
     Matrix a;
@@ -2565,7 +2566,8 @@
                           }
                       }
                     if (fl_mode > 0)
-                      set_normal (bfl_mode, n, j-1, i-1);
+                      set_normal (bfl_mode, (fl_mode == GOURAUD ? vn : fn),
+                                  j-1, i-1);
 
                     m_glfcns.glVertex3d (x(j1,i-1), y(j-1,i1), z(j-1,i-1));
 
@@ -2596,7 +2598,7 @@
                       }
 
                     if (fl_mode == GOURAUD)
-                      set_normal (bfl_mode, n, j-1, i);
+                      set_normal (bfl_mode, vn, j-1, i);
 
                     m_glfcns.glVertex3d (x(j1,i), y(j-1,i2), z(j-1,i));
 
@@ -2626,7 +2628,7 @@
                           }
                       }
                     if (fl_mode == GOURAUD)
-                      set_normal (bfl_mode, n, j, i);
+                      set_normal (bfl_mode, vn, j, i);
 
                     m_glfcns.glVertex3d (x(j2,i), y(j,i2), z(j,i));
 
@@ -2656,7 +2658,7 @@
                           }
                       }
                     if (fl_mode == GOURAUD)
-                      set_normal (bfl_mode, n, j, i-1);
+                      set_normal (bfl_mode, vn, j, i-1);
 
                     m_glfcns.glVertex3d (x(j2,i-1), y(j,i1), z(j,i-1));
 
@@ -2772,7 +2774,8 @@
                               }
                           }
                         if (el_mode > 0)
-                          set_normal (bfl_mode, n, j-1, i);
+                          set_normal (bfl_mode, (el_mode == GOURAUD ? vn : fn),
+                                      j-1, i);
 
                         m_glfcns.glVertex3d (x(j1,i), y(j-1,i2), z(j-1,i));
 
@@ -2799,7 +2802,7 @@
                               }
                           }
                         if (el_mode == GOURAUD)
-                          set_normal (bfl_mode, n, j, i);
+                          set_normal (bfl_mode, vn, j, i);
 
                         m_glfcns.glVertex3d (x(j2,i), y(j,i2), z(j,i));
 
@@ -2869,7 +2872,8 @@
                               }
                           }
                         if (el_mode > 0)
-                          set_normal (bfl_mode, n, j, i-1);
+                          set_normal (bfl_mode, (el_mode == GOURAUD ? vn : fn),
+                                      j, i-1);
 
                         m_glfcns.glVertex3d (x(j2,i-1), y(j,i1), z(j,i-1));
 
@@ -2896,7 +2900,7 @@
                               }
                           }
                         if (el_mode == GOURAUD)
-                          set_normal (bfl_mode, n, j, i);
+                          set_normal (bfl_mode, vn, j, i);
 
                         m_glfcns.glVertex3d (x(j2,i), y(j,i2), z(j,i));
 
--- a/libinterp/corefcn/graphics.cc	Sat Sep 08 17:23:57 2018 +0200
+++ b/libinterp/corefcn/graphics.cc	Sat Sep 08 20:36:30 2018 +0200
@@ -8677,8 +8677,7 @@
   std::list<graphics_object> children_list;
   std::list<graphics_object>::iterator children_list_iter;
   get_children_of_type ("patch", false, true, children_list);
-  // FIXME: Un-comment when surface is ready:
-  // get_children_of_type ("surface", false, true, children_list);
+  get_children_of_type ("surface", false, true, children_list);
 
   // trigger normals calculation for these objects
   for (children_list_iter = children_list.begin ();
@@ -8693,10 +8692,9 @@
         }
       else
         {
-          // FIXME: Un-comment when surface is ready:
-          // surface::properties& surface_props =
-          //     dynamic_cast<surface::properties&> (kid.get_properties ());
-          // surface_props.update_normals (false);
+          surface::properties& surface_props =
+              dynamic_cast<surface::properties&> (kid.get_properties ());
+          surface_props.update_normals (false);
         }
     }
 }
@@ -9312,8 +9310,8 @@
         {
           // more general for non-planar polygons
 
-          // calculate face normal with Newill method
-          // https://courses.cit.cornell.edu/cs417-land/SECTIONS/normals.html
+          // calculate face normal with Newell's method
+          // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method
 
           j1 = nc - 1; j2 = 0;
           i1 = f(i,j1) - 1; i2 = f(i,j2) - 1;
@@ -9505,9 +9503,91 @@
 }
 
 void
-surface::properties::update_vertex_normals (void)
-{
-  if (vertexnormalsmode_is ("auto"))
+surface::properties::update_face_normals (bool reset)
+{
+  if (! facenormalsmode_is ("auto"))
+    return;
+
+  if ((facelighting_is ("flat") || edgelighting_is ("flat")) &&
+      get_do_lighting ())
+    {
+      Matrix x = get_xdata ().matrix_value ();
+      Matrix y = get_ydata ().matrix_value ();
+      Matrix z = get_zdata ().matrix_value ();
+
+      int p = z.columns ();
+      int q = z.rows ();
+
+      // FIXME: There might be a cleaner way to do this.  When data is changed
+      // the update_xdata, update_ydata, update_zdata routines are called in a
+      // serial fashion.  Until the final call to update_zdata the matrices
+      // will be of mismatched dimensions which can cause an out-of-bound
+      // indexing in the code below.  This one-liner prevents calculating
+      // normals until dimensions match.
+      if (x.columns () != p || y.rows () != q)
+        return;
+
+      NDArray n (dim_vector (q-1, p-1, 3), 0.0);
+
+      bool x_mat = (x.rows () == q);
+      bool y_mat = (y.columns () == p);
+      
+      double dx = x(1,1) - x(0,0);
+      double dy = y(1,1) - y(0,0);
+
+      int i1, i2, j1, j2;
+      i1 = i2 = 0;
+      j1 = j2 = 0;
+
+      for (int i = 0; i < p-1; i++)
+        {
+          if (y_mat)
+            {
+              i1 = i;
+              i2 = i + 1;
+            }
+
+          for (int j = 0; j < q-1; j++)
+            {
+              if (x_mat)
+                {
+                  j1 = j;
+                  j2 = j + 1;
+                }
+
+              double& nx = n(j, i, 0);
+              double& ny = n(j, i, 1);
+              double& nz = n(j, i, 2);
+
+              // calculate face normal with Newell's method
+              // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal#Newell.27s_Method
+              
+              nx = dy * (z(j1,i1) + z(j2,i1) - z(j1,i2) - z(j2,i2));
+              ny = dx * (z(j1,i1) + z(j1,i2) - z(j2,i1) - z(j2,i2));
+              nz = 2 * dx * dy;
+
+              double d = std::max (std::max (fabs (nx), fabs (ny)), fabs (nz));
+
+              nx /= d;
+              ny /= d;
+              nz /= d;
+            }
+        }
+      facenormals = n;
+    }
+  else if (reset)
+    facenormals = Matrix ();
+}
+
+void
+surface::properties::update_vertex_normals (bool reset)
+{
+  if (! vertexnormalsmode_is ("auto"))
+    return;
+  
+  if ((facelighting_is ("gouraud") || facelighting_is ("phong") ||
+      edgelighting_is ("gouraud") || edgelighting_is ("phong")) &&
+      get_do_lighting ())
     {
       Matrix x = get_xdata ().matrix_value ();
       Matrix y = get_ydata ().matrix_value ();
@@ -9593,6 +9673,8 @@
         }
       vertexnormals = n;
     }
+  else if (reset)
+    vertexnormals = Matrix ();
 }
 
 // ---------------------------------------------------------------------
--- a/libinterp/corefcn/graphics.in.h	Sat Sep 08 17:23:57 2018 +0200
+++ b/libinterp/corefcn/graphics.in.h	Sat Sep 08 20:36:30 2018 +0200
@@ -5118,13 +5118,13 @@
       string_property displayname , ""
       double_radio_property edgealpha , double_radio_property (1.0, radio_values ("flat|interp"))
       color_property edgecolor , color_property (color_values (0, 0, 0), radio_values ("none|flat|interp"))
-      radio_property edgelighting , "{none}|flat|gouraud|phong"
+      radio_property edgelighting u , "{none}|flat|gouraud|phong"
       radio_property erasemode h , "{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 u , "none|{flat}|gouraud|phong"
       array_property facenormals m , Matrix ()
-      radio_property facenormalsmode , "{auto}|manual"
+      radio_property facenormalsmode u , "{auto}|manual"
       // FIXME: DEPRECATED: Remove interpreter property in version 6.
       radio_property interpreter hd , "{tex}|none|latex"
       radio_property linestyle , "{-}|--|:|-.|none"
@@ -5190,6 +5190,14 @@
       specularstrength.add_constraint ("max", 1.0, true);
     }
 
+  public:
+    void update_normals (bool reset)
+    {
+      update_face_normals (reset);
+      update_vertex_normals (reset);
+    }
+
+
   private:
     void update_alphadata (void)
     {
@@ -5209,26 +5217,38 @@
 
     void update_xdata (void)
     {
-      update_vertex_normals ();
+      update_normals (true);
       set_xlim (xdata.get_limits ());
     }
 
     void update_ydata (void)
     {
-      update_vertex_normals ();
+      update_normals (true);
       set_ylim (ydata.get_limits ());
     }
 
     void update_zdata (void)
     {
-      update_vertex_normals ();
+      update_normals (true);
       set_zlim (zdata.get_limits ());
     }
 
-    void update_vertex_normals (void);
+    void update_face_normals (bool reset);
+    void update_vertex_normals (bool reset);
+
+    void update_facenormalsmode (void)
+    { update_face_normals (false); }
 
     void update_vertexnormalsmode (void)
-    { update_vertex_normals (); }
+    { update_vertex_normals (false); }
+
+    void update_edgelighting (void)
+    { update_normals (false); }
+
+    void update_facelighting (void)
+    { update_normals (false); }
+
+
   };
 
 private:
--- a/scripts/plot/draw/light.m	Sat Sep 08 17:23:57 2018 +0200
+++ b/scripts/plot/draw/light.m	Sat Sep 08 20:36:30 2018 +0200
@@ -29,7 +29,9 @@
 ## objects are drawn with lighting effects.  Supported values for Lighting
 ## properties are @qcode{"none"} (no lighting effects), @qcode{"flat"} (faceted
 ## look of the objects), and @qcode{"gouraud"} (linear interpolation of the
-## lighting effects between the vertices).
+## lighting effects between the vertices).  If the lighting mode is set to
+## @qcode{"flat"}, the @qcode{"FaceNormals"} property is used for lighting.
+## For @qcode{"gouraud"}, the @qcode{"VertexNormals"} property is used.
 ##
 ## Up to eight light objects are supported per axes. (Implementation dependent)
 ##