# HG changeset patch
# User Colin Macdonald <cbm@m.fsf.org>
# Date 1468216940 25200
#      Sun Jul 10 23:02:20 2016 -0700
# Node ID 6dc7417f261490e5f428491a430d29e00ea9fa49
# Parent  a5a99a830c8c1d8ae46442048d15b91b6189feaf
Add camlight function (patch #9014).

* scripts/plot/draw/camlight.m: New function.
* scripts/plot/util/__rotate_around_axis__.m: New function, split from
rotate.m
* __unimplemented__.m: Remove camlight from list.
* scripts/plot/draw/module.mk: Update.
* doc/interpreter/plot.txi: Add docstring to manual.

diff -r a5a99a830c8c -r 6dc7417f2614 doc/interpreter/plot.txi
--- a/doc/interpreter/plot.txi	Thu Jul 07 13:55:37 2016 -0700
+++ b/doc/interpreter/plot.txi	Sun Jul 10 23:02:20 2016 -0700
@@ -390,6 +390,8 @@
 
 @DOCSTRING(specular)
 
+@DOCSTRING(camlight)
+
 @DOCSTRING(meshgrid)
 
 @DOCSTRING(ndgrid)
diff -r a5a99a830c8c -r 6dc7417f2614 scripts/help/__unimplemented__.m
--- a/scripts/help/__unimplemented__.m	Thu Jul 07 13:55:37 2016 -0700
+++ b/scripts/help/__unimplemented__.m	Sun Jul 10 23:02:20 2016 -0700
@@ -574,7 +574,6 @@
   "callSoapService",
   "camdolly",
   "cameratoolbar",
-  "camlight",
   "camlookat",
   "camorbit",
   "campan",
diff -r a5a99a830c8c -r 6dc7417f2614 scripts/plot/draw/camlight.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/camlight.m	Sun Jul 10 23:02:20 2016 -0700
@@ -0,0 +1,312 @@
+## Copyright (C) 2016 Colin B. Macdonald
+##
+## 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.
+##
+## This software 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 this software; see the file COPYING.
+## If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @documentencoding UTF-8
+## @deftypefn  {} {} camlight {}
+## @deftypefnx {} {} camlight right
+## @deftypefnx {} {} camlight left
+## @deftypefnx {} {} camlight headlight
+## @deftypefnx {} {} camlight (@var{az}, @var{el})
+## @deftypefnx {} {} camlight (@dots{}, @var{style})
+## @deftypefnx {} {@var{h} =} camlight (@dots{})
+## @deftypefnx {} {} camlight (@var{h}, @dots{})
+## Add lights to a figure using a simple interface.
+##
+## Example:
+## @example
+## @group
+## @c doctest: +SKIP
+## sphere (36)
+## camlight
+## @end group
+## @end example
+##
+## By default the light is positioned slightly above and to the
+## right of the camera's current position: this is the same as
+## @code{camlight right}.  The commands @code{camlight left} and
+## @code{camlight headlight} behave similarly.
+##
+## For more control, the light can also be positioned by an azimuthal
+## rotation @var{az} and an elevation angle @var{el}, both in degrees,
+## and both relative to the current properties of the camera.  Consider
+## the example:
+## @example
+## @group
+## @c doctest: +SKIP
+## camlight (45, 30)
+## @end group
+## @end example
+## Here the light is first pitched upwards from the
+## camera position by 30 degrees.  It is then yawed by
+## 45 degrees to the right.  Both rotations are centered around the
+## camera target.
+##
+## An optional string @var{style} specifies whether the light is a
+## local point source (@qcode{"local"}, the default) or placed at
+## infinite distance (@qcode{"infinite"}).
+##
+## A handle to the light can optionally be returned.  It can be used to
+## move or otherwise manipulate the light.  For example, we can create
+## a light on the left, move it to the right, finally changing its
+## colour:
+## @example
+## @group
+## @c doctest: +SKIP
+## clf
+## sphere (36)
+## h = camlight ("left")
+## drawnow ()
+## camlight (h, "right")
+## drawnow ()
+## set (h, "color", "r")
+## @end group
+## @end example
+##
+## @seealso{light}
+## @end deftypefn
+
+
+function hout = camlight (varargin)
+
+  # Is input 1 a light handle?  First check for two special cases where a
+  # double could coincidentally (and very rarely) correspond to a light:
+  #    camlight (az, el)
+  #    camlight (az, el, style)
+  # (Check can be removed when/if Octave stops using doubles for handles)
+  if ( (nargin == 2 && isnumeric (varargin{1}) && isnumeric (varargin{2}))
+       || (nargin == 3 && isnumeric (varargin{1}) && isnumeric (varargin{2})
+           && ischar (varargin{3})) )
+    h = [];
+  else
+    [h, varargin, nargin] = __plt_get_light_arg__ ("camlight", varargin{:});
+  endif
+
+  style = "local";
+
+  if (nargin == 0)
+    where = "right";
+
+  elseif (nargin == 1 && ischar (varargin{1}))
+    _v = varargin{1};
+    if (strcmp (_v, "local") || strcmp (_v, "infinite"))
+      style = _v;
+      where = "right";
+    else
+      where = _v;
+    endif
+
+  elseif (nargin == 2)
+    _v1 = varargin{1};
+    _v2 = varargin{2};
+    if (isnumeric (_v1) && isscalar (_v1) && isnumeric (_v2) && isscalar (_v2))
+      az = _v1;
+      el = _v2;
+    elseif (ischar (_v1) && ischar (_v2))
+      where = _v1;
+      style = _v2;
+    else
+      print_usage ();
+    endif
+
+  elseif (nargin == 3 && ischar (varargin{3})
+          && isnumeric (varargin{1}) && isscalar (varargin{1})
+          && isnumeric (varargin{2}) && isscalar (varargin{2}))
+    [az, el, style] = varargin{1:3};
+
+  else
+    print_usage ();
+  endif
+
+  if (exist ("where", "var"))
+    switch lower (where)
+      case "left"
+        az = -30;
+        el = 30;
+      case "right"
+        az = 30;
+        el = 30;
+      case "headlight"
+        az = 0;
+        el = 0;
+      otherwise
+        error ("Invalid camlight position")
+    endswitch
+  endif
+
+  cam_up = get (gca (), "cameraupvector");
+  cam_pos = get (gca (), "cameraposition");
+  cam_target = get (gca (), "cameratarget");
+
+  view_ax = cam_target - cam_pos;
+  view_ax = view_ax / norm (view_ax);
+  # orthogonalize the camup vector
+  yaw_ax = cam_up - view_ax*dot (cam_up, view_ax);
+  pitch_ax = cross (cam_up, view_ax);
+
+  # First pitch up by 'el', then yaw by 'az'.
+  # (order matters, this matches experiments with Matlab).
+  pos = num2cell(cam_pos);
+  [pos{:}] = __rotate_around_axis__ (pos{:}, el, pitch_ax, cam_target);
+  [pos{:}] = __rotate_around_axis__ (pos{:}, az, yaw_ax, cam_target);
+  pos = [pos{:}];
+
+  if (isempty (h))
+    htmp = light ("Position", pos, "style", style);
+  else
+    set (h, "Position", pos, "style", style);
+    htmp = h;
+  endif
+
+  if (nargout > 0)
+    hout = htmp;
+  endif
+endfunction
+
+
+%!demo
+%! %% Adding lights to a scene
+%! sphere (64);
+%! camlight
+%!
+%! %% add another light
+%! camlight left
+
+%!demo
+%! sphere (48)
+%! title ("This light has a fixed position, even if the camera moves");
+%! axis equal; shading flat
+%! view (30, 30)
+%!
+%! camlight
+%!
+%! for a = 30:2:390
+%!   view (a, 30)
+%!   drawnow ()
+%! end
+
+%!demo
+%! sphere (48)
+%! title ("Move the camera and update the light position");
+%! axis equal; shading flat
+%! view (30, 30)
+%!
+%! h = camlight ();          % keep a handle to the light
+%!
+%! for a = 30:2:390
+%!   view (a, 30)
+%!   camlight (h, "right")   % update light position
+%!   drawnow ()
+%! end
+
+
+%!error <Invalid> camlight ("wrong")
+%!error <Invalid> camlight ("wrong", "local")
+%!error <Invalid> camlight (1, 2, 3, 4, 5)
+
+%!shared hf, nh
+%! hf = figure ("visible", "off");
+%! do
+%!   nh = 100*rand ();
+%!   % ensure we have a non-handle double
+%! until (!ishandle (nh))
+
+%!error <invalid> camlight ("right", "gangnam")
+%!error <invalid> camlight (30, 30, "gangnam")
+%!error <Invalid> camlight (nh)
+%!error <Invalid> camlight (nh, "right")
+%!error <Invalid> camlight (nh, "local")
+%!error <Invalid> camlight (nh, 30, 30, "local")
+
+%!test
+%! clf; sphere (24);
+%! set (gca (), "cameraposition", [8 12 7.3])
+%! set (gca (), "cameraupvector", [0 2 2])
+%! set (gca (), "cameratarget", [0.5 -0.3 -0.3])
+%! h = camlight (45, 20);
+%! A = get (h, "position");
+%! % From maillist, someone tested on Matlab R2015b for OSX:
+%! B = [-3.301207088157029 15.474861455795917 1.115828634895176];
+%! assert (A, B, -20*eps)
+%!test
+%! h = camlight (300, -190);
+%! A = get (h, "position");
+%! B = [-11.054849015640563 2.931330143100648 -11.315623892092518];
+%! assert (A, B, -20*eps)
+
+%!test
+%! % move a light
+%! h = camlight ("left");
+%! h2 = camlight ();
+%! p2 = get (h2, "position");
+%! camlight (h, "right")
+%! p = get (h, "position");
+%! assert (p, p2)
+%! camlight (h)
+%! p = get (h, "position");
+%! assert (p, p2)
+
+%!test
+%! % Should be a new light at az == h + 10, el == 20
+%! h = camlight ();
+%! camlight (double (h) + 10, 20)
+
+%!test
+%! % Should be a new light at az == h, el == 20
+%! h = camlight ();
+%! camlight (double (h), 20)
+
+%!test
+%! % Should be a new light at az == h, el == 20
+%! h = camlight ();
+%! camlight (double (h), 20, "local")
+
+%!test
+%! % updating style also moves light
+%! clf
+%! h1 = camlight ("right");
+%! h = camlight ("headlight");
+%! p1 = get (h1, "position");
+%! h2 = camlight (h, "local");
+%! p2 = get (h2, "position");
+%! assert (h, h2)
+%! assert (p1, p2)
+
+%!test
+%! close (hf);
+
+
+function [h, varargin, narg] = __plt_get_light_arg__ (caller, varargin)
+
+  h = [];
+
+  ## Look for a scalar which is a graphics handle but not the
+  ## Root Figure (0) or an ordinary figure (integer).
+  if (numel (varargin) > 0 && numel (varargin{1}) == 1
+      && ishandle (varargin{1}) && varargin{1} != 0 && ! isfigure (varargin{1}))
+    h = varargin{1};
+    islight = strcmp (get (h, "type"), "light");
+    if (! islight)
+      error ("%s: if first arg is a handle, it must be a light handle", caller);
+    endif
+    varargin(1) = [];
+  endif
+
+  narg = length (varargin);
+
+endfunction
diff -r a5a99a830c8c -r 6dc7417f2614 scripts/plot/draw/module.mk
--- a/scripts/plot/draw/module.mk	Thu Jul 07 13:55:37 2016 -0700
+++ b/scripts/plot/draw/module.mk	Sun Jul 10 23:02:20 2016 -0700
@@ -24,6 +24,7 @@
   scripts/plot/draw/area.m \
   scripts/plot/draw/barh.m \
   scripts/plot/draw/bar.m \
+  scripts/plot/draw/camlight.m \
   scripts/plot/draw/colorbar.m \
   scripts/plot/draw/comet3.m \
   scripts/plot/draw/comet.m \
diff -r a5a99a830c8c -r 6dc7417f2614 scripts/plot/util/__rotate_around_axis__.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/util/__rotate_around_axis__.m	Sun Jul 10 23:02:20 2016 -0700
@@ -0,0 +1,61 @@
+## Copyright (C) 2014-2015 John W. Eaton
+## Copyright (C) 2016 Colin B. Macdonald
+##
+## 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.
+##
+## This software 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 this software; see the file COPYING.
+## If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {[@var{xr}, @var{yr}, @var{zr}] =} __rotate_around_axis__ (@var{x}, @var{y}, @var{z}, @var{angle}, @var{dir}, @var{origin})
+## Undocumented internal function.
+## @end deftypefn
+
+
+function [xr, yr, zr] = __rotate_around_axis__ (x, y, z, angle, dir, origin);
+  # Rotate the points given by X, Y, Z about an axis by ANGLE degrees.
+  # The axis is specified by vector DIR and point ORIGIN.
+  # Vectorized over X, Y, Z.
+
+  dir /= norm (dir);
+  u = dir(1);
+  v = dir(2);
+  w = dir(3);
+
+  a = origin(1);
+  b = origin(2);
+  c = origin(3);
+
+  sa = sind (angle);
+  ca = cosd (angle);
+
+  if (a == 0 && b == 0 && c == 0)
+    tmp = (u*x + v*y + w*z) * (1 - ca);
+
+    xr = u*tmp + x*ca + (-w*y + v*z)*sa;
+    yr = v*tmp + y*ca + (w*x - u*z)*sa;
+    zr = w*tmp + z*ca + (-v*x + u*y)*sa;
+  else
+    one_m_ca = 1 - ca;
+    tmp = u*x + v*y + w*z;
+
+    xr = ((a*(v**2 + w**2) - u*(b*v + c*w - tmp))*one_m_ca
+          + x*ca + (-c*v + b*w - w*y + v*z)*sa);
+    yr = ((b*(u**2 + w**2) - v*(a*u + c*w - tmp))*one_m_ca
+          + y*ca + (c*u - a*w + w*x - u*z)*sa);
+    zr = ((c*(u**2 + v**2) - w*(a*u + b*v - tmp))*one_m_ca
+          + z*ca + (-b*u + a*v - v*x + u*y)*sa);
+  endif
+
+endfunction
