# HG changeset patch
# User Markus Muetzel <markus.muetzel@gmx.de>
# Date 1468859628 -7200
#      Mon Jul 18 18:33:48 2016 +0200
# Node ID df662fa87041e68dd7500f331198e16717fef64d
# Parent  5c949eecb6dd674b63bed84b478163028f9ac912
Enable support of non-planar faces in patches for OpenGL (bug #47677)

* graphics.in.h (patch::properties): Add property coplanar_last_idx.
* graphics.cc (patch::properties::update_data): Calculate and store last
indices of coplanar subsets of faces.
* gl-render.cc (opengl_renderer::draw_patch): Draw coplanar subsets of
non-planar faces separately.
* patch.m: Add demo with non-planar patch.

diff -r 5c949eecb6dd -r df662fa87041 libinterp/corefcn/gl-render.cc
--- a/libinterp/corefcn/gl-render.cc	Sat Jul 02 10:19:44 2016 -0400
+++ b/libinterp/corefcn/gl-render.cc	Mon Jul 18 18:33:48 2016 +0200
@@ -2701,6 +2701,8 @@
   if (fl_mode > 0 || el_mode > 0)
     glMaterialf (LIGHT_MODE, GL_SHININESS, se);
 
+  std::list<std::list<octave_idx_type>>::const_iterator it1;
+
   if (! props.facecolor_is ("none"))
     {
       // FIXME: adapt to double-radio property
@@ -2736,59 +2738,93 @@
           // planes at +2.5.
           patch_tesselator tess (this, fc_mode, fl_mode, 1.0);
 
+          it1 = props.coplanar_last_idx.begin ();
+          std::list<octave_idx_type>::const_iterator it2;
+          octave_idx_type i_start, i_end;
           for (int i = 0; i < nf; i++)
             {
               if (clip_f(i))
                 continue;
 
-              tess.begin_polygon (true);
-              tess.begin_contour ();
-
-              // Add vertices in reverse order for Matlab compatibility
-              for (int j = count_f(i)-1; j > 0; j--)
+              // loop over planar subsets of face
+              bool check_coplanarity = false;
+              if (has_z && count_f(i) > 3)
                 {
-                  vertex_data::vertex_data_rep *vv = vdata[i+j*fr].get_rep ();
-
-                  tess.add_vertex (vv->coords.fortran_vec (), vv);
+                  check_coplanarity = true;
+                  it2 = (*it1).end ();
+                  it2--;
                 }
-
-              if (count_f(i) > 0)
+              do
                 {
-                  vertex_data::vertex_data_rep *vv = vdata[i].get_rep ();
-
-                  if (fc_mode == FLAT)
+                  if (check_coplanarity)
                     {
-                      // For "flat" shading, use color of 1st vertex.
-                      Matrix col = vv->color;
-
-                      if (col.numel () == 3)
+                      i_end = *it2;
+                      if (it2 == (*it1).begin ())
+                        i_start = 0;
+                      else
                         {
-                          glColor3dv (col.data ());
-                          if (fl_mode > 0)
+                          it2--;
+                          i_start = *it2 - 1;
+                        }
+                    }
+                  else
+                    {
+                      i_end = count_f(i) - 1;
+                      i_start = 0;
+                    }
+
+                  tess.begin_polygon (true);
+                  tess.begin_contour ();
+
+                  // Add vertices in reverse order for Matlab compatibility
+                  for (int j = i_end; j > i_start; j--)
+                    {
+                      vertex_data::vertex_data_rep *vv = vdata[i+j*fr].get_rep ();
+                      tess.add_vertex (vv->coords.fortran_vec (), vv);
+                    }
+                  vertex_data::vertex_data_rep *vv0 = vdata[i].get_rep ();
+                  tess.add_vertex (vv0->coords.fortran_vec (), vv0);
+
+                  if (count_f(i) > 0)
+                    {
+                      vertex_data::vertex_data_rep *vv = vdata[i].get_rep ();
+
+                      if (fc_mode == FLAT)
+                        {
+                          // For "flat" shading, use color of 1st vertex.
+                          Matrix col = vv->color;
+
+                          if (col.numel () == 3)
                             {
-                              float cb[4] = { 0, 0, 0, 1 };
-
-                              for (int k = 0; k < 3; k++)
-                                cb[k] = (vv->ambient * col(k));
-                              glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);
-
-                              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);
+                              glColor3dv (col.data ());
+                              if (fl_mode > 0)
+                                {
+                                  float cb[4] = { 0, 0, 0, 1 };
+
+                                  for (int k = 0; k < 3; k++)
+                                    cb[k] = (vv->ambient * col(k));
+                                  glMaterialfv (LIGHT_MODE, GL_AMBIENT, cb);
+
+                                  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);
+                                }
                             }
                         }
+
+                      tess.add_vertex (vv->coords.fortran_vec (), vv);
                     }
 
-                  tess.add_vertex (vv->coords.fortran_vec (), vv);
-                }
-
-              tess.end_contour ();
-              tess.end_polygon ();
+                  tess.end_contour ();
+                  tess.end_polygon ();
+                } while (i_start > 0);
+              if (check_coplanarity)
+                it1++;
             }
 
           if ((fl_mode > 0) && (num_lights > 0) && has_normals)
@@ -2839,11 +2875,17 @@
           // not supported by glPolygonOffset which is used to do Z offsets.
           patch_tesselator tess (this, ec_mode, el_mode);
 
+          it1 = props.coplanar_last_idx.begin ();
+
           for (int i = 0; i < nf; i++)
             {
-              if (clip_f(i))
+              bool is_non_planar = false;
+              if ((has_z && count_f(i) > 3) && (*it1).front () != count_f(i))
+                is_non_planar = true;
+              if (clip_f(i) || is_non_planar)
                 {
-                  // This is an unclosed contour.  Draw it as a line.
+                  // This is an unclosed contour or a non-planar face.
+                  // Draw it as a line.
                   bool flag = false;
 
                   glShadeModel ((ec_mode == INTERP || el_mode == GOURAUD)
@@ -2913,6 +2955,8 @@
                   tess.end_contour ();
                   tess.end_polygon ();
                 }
+              if (has_z && count_f(i) > 3)
+                it1++;
             }
 
           set_linestyle ("-");
diff -r 5c949eecb6dd -r df662fa87041 libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Sat Jul 02 10:19:44 2016 -0400
+++ b/libinterp/corefcn/graphics.cc	Mon Jul 18 18:33:48 2016 +0200
@@ -8234,6 +8234,48 @@
         }
     }
 
+  // check coplanarity for 3d-faces with more than 3 corners
+  int fcmax = idx.rows ();
+  if (fcmax > 3 && vert.columns () > 2)
+    {
+      for (octave_idx_type jj = 0; jj < idx.columns (); jj++)
+        {
+          if (! octave::math::isnan (idx(3,jj)))
+            {
+              // find first element that is NaN to get number of corners
+              octave_idx_type nc = 3;
+              while (! octave::math::isnan (idx(nc,jj)) && nc < fcmax)
+                nc++;
+
+              std::list<octave_idx_type> coplanar_ends;
+
+              octave_idx_type i_start = 1;
+              octave_idx_type i_end = 2;
+              while (i_end < nc - 1)
+                {
+                  // look for coplanar subsets
+                  for (i_end = nc-1; i_end > i_start+1; i_end--)
+                    {
+                      Matrix fc = Matrix (i_end - i_start + 1, 3, 0.0);
+                      for (octave_idx_type j = 0; j <= i_end-i_start; j++)
+                        for (octave_idx_type i = 0; i < 3; i++)
+                          fc(j,i) = vert(idx(j + i_start,jj)-1,i) - vert(idx(0,jj)-1,i);
+
+                      // calculate rank of matrix
+                      svd<Matrix> result (fc, svd<Matrix>::sigma_only, svd<Matrix>::GESVD);
+                      DiagMatrix sigma = result.singular_values ();
+                      double tol = nc * sigma(0,0) * std::numeric_limits<double>::epsilon ();
+                      if (sigma(2,2) < tol)
+                        break;
+                    }
+                  coplanar_ends.push_back (i_end);
+                  i_start = i_end;
+                }
+              coplanar_last_idx.push_back (coplanar_ends);
+            }
+        }
+    }
+
   // Build cdata
   dim_vector dv = dim_vector::alloc (3);
   NDArray cd;
diff -r 5c949eecb6dd -r df662fa87041 libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Sat Jul 02 10:19:44 2016 -0400
+++ b/libinterp/corefcn/graphics.in.h	Mon Jul 18 18:33:48 2016 +0200
@@ -4884,6 +4884,8 @@
     std::string get_climinclude (void) const
     { return climinclude.current_value (); }
 
+    std::list<std::list<octave_idx_type>> coplanar_last_idx;
+
     // See the genprops.awk script for an explanation of the
     // properties declarations.
     // Programming note: Keep property list sorted if new ones are added.
diff -r 5c949eecb6dd -r df662fa87041 scripts/plot/draw/patch.m
--- a/scripts/plot/draw/patch.m	Sat Jul 02 10:19:44 2016 -0400
+++ b/scripts/plot/draw/patch.m	Mon Jul 18 18:33:48 2016 +0200
@@ -247,6 +247,25 @@
 %! p = patch (x, y, [1 32]);
 %! title ('Autoscaling of colors: Red UL and Blue LR triangles');
 
+%!demo
+%! clf;
+%! vertices = [0 0 0; 0.5 -0.5 0; 1 0 0; 1 1 0; 0 1 1; 1 0 1; 0 -1 0;]+3;
+%! faces = [1 2 3 4 5 6 7];
+%! ha = axes ();
+%! hp = patch ('Vertices', vertices, 'Faces', faces, 'FaceColor', 'g');
+%! xlabel('x'), ylabel('y'), zlabel('z')
+%! view(3)
+%! set (ha, "XTick", [], "YTick", [], "ZTick", [])
+%! text (vertices(1,1), vertices(1,2), vertices(1,3), "1")
+%! text (vertices(2,1), vertices(2,2), vertices(2,3), "2")
+%! text (vertices(3,1), vertices(3,2), vertices(3,3), "3")
+%! text (vertices(4,1), vertices(4,2), vertices(4,3), "4")
+%! text (vertices(5,1), vertices(5,2), vertices(5,3), "5")
+%! text (vertices(6,1), vertices(6,2), vertices(6,3), "6")
+%! text (vertices(7,1), vertices(7,2), vertices(7,3), "7")
+%! title ("Non-planar patch")
+
+
 %!test
 %! hf = figure ("visible", "off");
 %! unwind_protect
