1########################################################################
2##
3## Copyright (C) 2009-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} {} surfl (@var{z})
28## @deftypefnx {} {} surfl (@var{x}, @var{y}, @var{z})
29## @deftypefnx {} {} surfl (@dots{}, @var{lsrc})
30## @deftypefnx {} {} surfl (@var{x}, @var{y}, @var{z}, @var{lsrc}, @var{P})
31## @deftypefnx {} {} surfl (@dots{}, "cdata")
32## @deftypefnx {} {} surfl (@dots{}, "light")
33## @deftypefnx {} {} surfl (@var{hax}, @dots{})
34## @deftypefnx {} {@var{h} =} surfl (@dots{})
35## Plot a 3-D surface using shading based on various lighting models.
36##
37## The surface mesh is plotted using shaded rectangles.  The vertices of the
38## rectangles [@var{x}, @var{y}] are typically the output of @code{meshgrid}.
39## over a 2-D rectangular region in the x-y plane.  @var{z} determines the
40## height above the plane of each vertex.  If only a single @var{z} matrix is
41## given, then it is plotted over the meshgrid
42## @code{@var{x} = 1:columns (@var{z}), @var{y} = 1:rows (@var{z})}.
43## Thus, columns of @var{z} correspond to different @var{x} values and rows
44## of @var{z} correspond to different @var{y} values.
45##
46## The default lighting mode @qcode{"cdata"}, changes the cdata property of the
47## surface object to give the impression of a lighted surface.
48##
49## The alternate mode @qcode{"light"} creates a light object to illuminate the
50## surface.
51##
52## The light source location may be specified using @var{lsrc} which can be
53## a 2-element vector [azimuth, elevation] in degrees, or a 3-element vector
54## [lx, ly, lz].  The default value is rotated 45 degrees counterclockwise to
55## the current view.
56##
57## The material properties of the surface can specified using a 4-element
58## vector @var{P} = [@var{AM} @var{D} @var{SP} @var{exp}] which defaults to
59## @var{p} = [0.55 0.6 0.4 10].
60##
61## @table @asis
62## @item @qcode{"AM"} strength of ambient light
63##
64## @item @qcode{"D"} strength of diffuse reflection
65##
66## @item @qcode{"SP"} strength of specular reflection
67##
68## @item @qcode{"EXP"} specular exponent
69## @end table
70##
71## If the first argument @var{hax} is an axes handle, then plot into this axes,
72## rather than the current axes returned by @code{gca}.
73##
74## The optional return value @var{h} is a graphics handle to the created
75## surface object.
76##
77## Example:
78##
79## @example
80## @group
81## colormap (bone (64));
82## surfl (peaks);
83## shading interp;
84## @end group
85## @end example
86## @seealso{diffuse, specular, surf, shading, colormap, caxis}
87## @end deftypefn
88
89function h = surfl (varargin)
90
91  [hax, varargin, nargin] = __plt_get_axis_arg__ ("surfl", varargin{:});
92
93  if (nargin == 0)
94    print_usage ();
95  endif
96
97  ## Check for lighting type.
98  use_cdata = true;
99  if (ischar (varargin{end}))
100    switch (tolower (varargin{end}))
101      case "light"
102        use_cdata = false;
103      case "cdata"
104        use_cdata = true;
105      otherwise
106        error ("surfl: unknown lighting method");
107    endswitch
108    varargin(end) = [];
109  endif
110
111  ## Check for reflection properties argument.
112  ##
113  ## r = [ambient light strength,
114  ##      diffuse reflection strength,
115  ##      specular reflection strength,
116  ##      specular shine]
117  if (isnumeric (varargin{end}) && isvector (varargin{end})
118      && (numel (varargin{end}) == 4))
119    r = varargin{end};
120    varargin(end) = [];
121  else
122    ## Default values.
123    r = [0.55, 0.6, 0.4, 10];
124  endif
125
126  ## Check for light vector (lv) argument.
127  have_lv = false;
128  if (isnumeric (varargin{end}) && isvector (varargin{end}))
129    len = numel (varargin{end});
130    lastarg = varargin{end};
131    if (len == 3)
132      lv = lastarg;
133      varargin(end) = [];
134      have_lv = true;
135    elseif (len == 2)
136      [lv(1), lv(2), lv(3)] = sph2cart ((lastarg(1) - 90) * pi/180,
137                                         lastarg(2) * pi/180,
138                                         1.0);
139      varargin(end) = [];
140      have_lv = true;
141    endif
142  endif
143
144  oldfig = [];
145  if (! isempty (hax))
146    oldfig = get (0, "currentfigure");
147  endif
148  unwind_protect
149    hax = newplot (hax);
150
151    htmp = surface (varargin{:});
152    if (! ishold ())
153      set (hax, "view", [-37.5, 30],
154                "xgrid", "on", "ygrid", "on", "zgrid", "on");
155    endif
156
157    ## Get view vector (vv).
158    [az, el] = view ();
159    vv = sph2cart ((az - 90) * pi/180.0, el * pi/180.0, 1.0);
160
161    if (! have_lv)
162      ## Calculate light vector (lv) from view vector.
163      phi = pi / 4;  # 45 degrees
164      R = [cos(phi), -sin(phi), 0;
165           sin(phi),  cos(phi), 0;
166           0,         0,        1];
167      lv = (R * vv.').';
168    endif
169
170    if (use_cdata)
171      set (hax, "clim", [0 1]);
172
173      __update_normals__ (htmp);
174      vn = get (htmp, "vertexnormals");
175      dar = get (hax, "dataaspectratio");
176      vn(:,:,1) *= dar(1);
177      vn(:,:,2) *= dar(2);
178      vn(:,:,3) *= dar(3);
179
180      ## Normalize vn.
181      vn ./= repmat (sqrt (sumsq (vn, 3)), [1, 1, 3]);
182      [nr, nc] = size (get (htmp, "zdata"));
183
184      ## Ambient, diffuse, and specular term.
185      cdata = (  r(1) * ones (nr, nc)
186               + r(2) * diffuse  (vn(:,:,1), vn(:,:,2), vn(:,:,3), lv)
187               + r(3) * specular (vn(:,:,1), vn(:,:,2), vn(:,:,3), lv, vv, r(4)));
188      cdata ./= sum (r(1:3));
189
190      set (htmp, "cdata", cdata);
191    else
192      light (hax, "position", lv);
193      set (htmp, "ambientstrength", r(1), "diffusestrength", r(2), ...
194                 "specularstrength", r(3), "specularexponent", r(4));
195    endif
196
197  unwind_protect_cleanup
198    if (! isempty (oldfig))
199      set (0, "currentfigure", oldfig);
200    endif
201  end_unwind_protect
202
203  if (nargout > 0)
204    h = htmp;
205  endif
206
207endfunction
208
209
210%!demo
211%! clf;
212%! [X,Y,Z] = sombrero ();
213%! colormap (copper (64));
214%! surfl (X,Y,Z);
215%! shading interp;
216%! title ("surfl() with defaults");
217
218%!demo
219%! clf;
220%! [X,Y,Z] = sombrero ();
221%! colormap (copper (64));
222%! surfl (X,Y,Z, [62.50,30], [0.2 0.6 0.4 25]);
223%! shading interp;
224%! title ("surfl() with lighting vector and material properties");
225
226%!demo
227%! clf;
228%! [X, Y] = meshgrid (-3:1/8:3);
229%! Z = peaks (X, Y);
230%! surfl (X, Y, Z, "light");
231%! shading interp;
232%! title ("surfl() with light object");
233