%% 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 {} {@var{h} =} camlight (@dots{})
%% @deftypefnx {} {} camlight (@var{h}, @dots{})
%% Add lights to a figure using a simple interface.
%%
%% Example:
%% @example
%% @c doctest: +SKIP
%% @group
%% 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: the same as
%% @code{camlight right}.
%%
%% A handle to the light can optionally be returned.  It can be
%% used to further manipulate the light.  For example, we can create
%% a light on the left and then move it to the right:
%% @example
%% @c doctest: +SKIP
%% @group
%% clf
%% sphere (36)
%% h = camlight ('left')
%% drawnow ()
%% camlight (h, 'right')
%% drawnow ()
%% set (h, 'color', 'r')
%% @end group
%% @end example
%% For changing the color of the light @pxref{light}.
%%
%% TODO: style kwarg.
%%
%% @seealso{lighting, light, material}
%% @end deftypefn


function h = camlight (varargin)

  if ( (nargin == 0) || (! ishandle (varargin{1})) )
    hin = [];  % no handle
    z = 0;     % first input
  else
    hin = varargin{1};
    z = 1;
  end

  if (nargin == z)
    s = 'right';
  elseif (nargin == z + 1)
    s = varargin{z + 1};
    if (! ischar (s))
      print_usage ();
    end
  elseif (nargin == z + 2)
    s = '_skip';
    az = varargin{z + 1};
    el = varargin{z + 2};
  else
    print_usage ();
  end

  switch lower(s)
    case 'left'
      % TODO: compare to Matlab
      az = -45;
      el = 30;
    case 'right'
      az = 45;
      el = 30;
    case 'headlight'
      az = 0;
      el = 0;
    case '_skip'
    otherwise
      error ('Invalid camlight position')
  end

  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);
  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).
  A = rotateAxis(cam_pos, el, pitch_ax, cam_target);
  light_pos = rotateAxis(A, az, yaw_ax, cam_target);

  if (isempty (hin))
    htmp = light ('Position', light_pos);
  else
    set (hin, 'Position', light_pos);
    htmp = hin;
  end

  if (nargout > 0)
    h = htmp;
  end
end


%!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 ('wtf')
%!error <Invalid> camlight (42)

%!shared hf
%! hf = figure ('visible', 'off');
%!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
%! close (hf);


function xxr = rotateAxis(xx, angle, dir, origin);
  % rotate the point XX about axis DIR by ANGLE degrees.
  dir = dir / norm(dir);
  x = xx(1);
  y = xx(2);
  z = xx(3);

  % this code is copied from @pxref{rotate}.
  u = dir(1);
  v = dir(2);
  w = dir(3);

  a = origin(1);
  b = origin(2);
  c = origin(3);

  sa = sind (angle);
  ca = cosd (angle);

  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);
  xxr = xx;
  xxr(1) = xr;
  xxr(2) = yr;
  xxr(3) = zr;
end


%!assert false
