# HG changeset patch
# User Colin Macdonald <cbm@m.fsf.org>
# Date 1468387194 25200
#      Tue Jul 12 22:19:54 2016 -0700
# Node ID 990224cf7ea36337deaee5bd6c14b1d0a82533b3
# Parent  6a616814e0c1d97840831dca9a46f3fca2b2e949
Add camzoom and camroll functions.

These don't precisely match Matlab's behaviour: added some xtests
against someone's Matlab output.  But probably good enough for
interactive use.

* doc/interpreter/plot.txi: Add docstrings to manual.
* scripts/help/__unimplemented__.m: Trim list.
* scripts/plot/appearance/camroll.m: New function.
* scripts/plot/appearance/camup.m: Docs now suggest camroll.
* scripts/plot/appearance/camzoom.m: New function.
* scripts/plot/appearance/module.mk: Update.

diff -r 6a616814e0c1 -r 990224cf7ea3 doc/interpreter/plot.txi
--- a/doc/interpreter/plot.txi	Mon Jul 11 19:14:07 2016 -0700
+++ b/doc/interpreter/plot.txi	Tue Jul 12 22:19:54 2016 -0700
@@ -402,12 +402,16 @@
 
 @DOCSTRING(campos)
 
+@DOCSTRING(camroll)
+
 @DOCSTRING(camtarget)
 
 @DOCSTRING(camup)
 
 @DOCSTRING(camva)
 
+@DOCSTRING(camzoom)
+
 @DOCSTRING(slice)
 
 @DOCSTRING(ribbon)
diff -r 6a616814e0c1 -r 990224cf7ea3 scripts/help/__unimplemented__.m
--- a/scripts/help/__unimplemented__.m	Mon Jul 11 19:14:07 2016 -0700
+++ b/scripts/help/__unimplemented__.m	Tue Jul 12 22:19:54 2016 -0700
@@ -578,8 +578,6 @@
   "camorbit",
   "campan",
   "camproj",
-  "camroll",
-  "camzoom",
   "categorical",
   "categories",
   "cdf2rdf",
diff -r 6a616814e0c1 -r 990224cf7ea3 scripts/plot/appearance/camroll.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/appearance/camroll.m	Tue Jul 12 22:19:54 2016 -0700
@@ -0,0 +1,142 @@
+## 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  {} {} camroll (@var{theta})
+## @deftypefnx {} {} camroll (@var{ax}, @var{theta})
+## Roll the camera.
+##
+## Roll the camera clockwise by @var{theta} degrees.
+## For example, the following command will roll the camera by
+## 30 degrees clockwise (to the right); this will cause the scene
+## to appear to roll by 30 degrees to the left:
+## @example
+## @group
+## @c doctest: +SKIP
+## peaks ()
+## camroll (30)
+## @end group
+## @end example
+##
+## Roll the camera back:
+## @example
+## @group
+## @c doctest: +SKIP
+## camroll (-30)
+## @end group
+## @end example
+##
+## To restore the automatic behaviour, we need a different command:
+## @example
+## @group
+## @c doctest: +SKIP
+## camup ("auto")
+## @end group
+## @end example
+##
+## By default, these commands effect the current axis; alternatively
+## an axis can be specified by the optional argument @var{ax}.
+##
+## @seealso{camzoom, camup}
+## @end deftypefn
+
+
+function p = camroll (varargin)
+
+  [hax, varargin, nargin] = __plt_get_axis_arg__ ("camroll", varargin{:});
+
+  if (nargin != 1)
+    print_usage ();
+  endif
+
+  a = varargin{1};
+
+  if (! (isnumeric (a) && isscalar (a) ))
+    print_usage ();
+  endif
+
+  if (isempty (hax))
+    hax = gca ();
+  else
+    hax = hax(1);
+  endif
+
+  view_ax = camtarget (hax) - campos (hax);
+  view_ax /= norm (view_ax);
+  ## orthogonalize the camup vector
+  up = camup (hax) - view_ax*dot (camup (hax), view_ax);
+  up /= norm (up);
+
+  ## rotate the modified camup vector around the view axis
+  up = num2cell (up);
+  [up{:}] = __rotate_around_axis__ (up{:}, a, view_ax, [0 0 0]);
+  up = [up{:}];
+  camup (hax, up)
+
+endfunction
+
+
+%!demo
+%! peaks ()
+%! camroll (30)
+
+
+%!error <Invalid> camroll (1, 2, 3)
+%!error <Invalid> camroll ("mod")
+%!error <Invalid> camroll (1, [1 2])
+
+%!shared hf
+%! hf = figure ("visible", "off");
+
+%!test
+%! sphere (10)
+%! campos ([10 0 0])
+%! camroll (30)
+%! p = camup ();
+%! assert (p, [0 1/2 sqrt(3)/2], eps)
+
+%!test
+%! ## test rolling, then rolling back
+%! clf; peaks ()
+%! p = camup ();
+%! assert (p, [0 0 1], eps)
+%! camroll (30)
+%! p = camup ();
+%! ## from Matlab R2014a
+%! q = [0.826398839602911  0.255644120004753  0.50170812412194];
+%! assert (p, q, 10*eps)
+%! camroll (-30)
+%! ## note it does not go back to [0 0 1]: instead orthog to camera view:
+%! p = camup ();
+%! assert (dot (p, camtarget () - campos ()), 0, eps)
+%! q = [0.496200420425837  0.646660977913424  0.57932264103285];
+%! assert (p, q, 10*eps)
+
+%!test
+%! ## test ax input by creating another axis
+%! clf;
+%! subplot (1, 2, 1); sphere (); hax = gca ();
+%! subplot (1, 2, 2); sphere (); camroll (hax, 30)
+%! subplot (1, 2, 1); x = camup ();
+%! subplot (1, 2, 2); y = camup ();
+%! assert (x, [0.660278 0.039151 0.750000], -1e-5)
+%! assert (y, [0 0 1])
+
+%!test
+%! close (hf)
diff -r 6a616814e0c1 -r 990224cf7ea3 scripts/plot/appearance/camup.m
--- a/scripts/plot/appearance/camup.m	Mon Jul 11 19:14:07 2016 -0700
+++ b/scripts/plot/appearance/camup.m	Tue Jul 12 22:19:54 2016 -0700
@@ -59,7 +59,9 @@
 ## @end example
 ## A consequence is that ``pulling back'' on the up vector does not
 ## pitch the camera view (as that would require changing the
-## target)
+## target).  Setting the up vector is thus typically used only to
+## roll the camera; and for this purpose there is a more
+## intuitive command, @pxref{camroll}.
 ##
 ## Finally, we can reset the up vector to automatic mode:
 ## @example
diff -r 6a616814e0c1 -r 990224cf7ea3 scripts/plot/appearance/camzoom.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/appearance/camzoom.m	Tue Jul 12 22:19:54 2016 -0700
@@ -0,0 +1,142 @@
+## 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  {} {} camzoom (@var{f})
+## @deftypefnx {} {} camzoom (@var{ax}, @var{f})
+## Zoom the camera in or out.
+##
+## A value of @var{f} larger than 1 ``zooms in'' and we see more
+## of the middle part of the scene:
+## @example
+## @group
+## hf = figure();
+## sphere (36)
+## camzoom (1.2)
+## @end group
+## @end example
+##
+## A value smaller than 1 ``zooms out'' so the camera can see more
+## of the scene:
+## @example
+## @group
+## camzoom (0.5)
+## @end group
+## @end example
+##
+## Technically speaking, zooming effects the ``viewing angle'' and
+## this gives a way to reset to the default zoom:
+## @example
+## @group
+## camva ("auto")
+## close (hf)
+## @end group
+## @end example
+##
+## By default, these commands effect the current axis; alternatively
+## an axis can be specified by the optional argument @var{ax}.
+##
+## @seealso{camroll, camva}
+## @end deftypefn
+
+
+function p = camzoom (varargin)
+
+  [hax, varargin, nargin] = __plt_get_axis_arg__ ("camva", varargin{:});
+
+  if (nargin != 1)
+    print_usage ();
+  endif
+
+  f = varargin{1};
+
+  if (! (isnumeric (f) && isscalar (f) ))
+    print_usage ();
+  endif
+
+  if (isempty (hax))
+    hax = gca ();
+  else
+    hax = hax(1);
+  endif
+
+  a = camva (hax);
+  # XXX: I don't think this quite matches Matlab, what is the standard
+  # meaning of zoom factor?  Maybe they do that.
+  camva (hax, a/f);
+
+endfunction
+
+
+%!demo
+%! peaks ()
+%! camzoom (2)
+
+
+%!error <Invalid> camzoom (1, 2, 3)
+%!error <Invalid> camzoom (1, [2, 3])
+%!error <Invalid> camzoom ("string")
+
+%!shared hf
+%! hf = figure ("visible", "off");
+
+%!test
+%! sphere ()
+%! x = camva ();
+%! camzoom (5)
+%! assert (abs (x - camva ()) > x/3)
+%! camzoom (1/5)
+%! y = camva ();
+%! assert (x, y, eps)
+
+%!xtest
+%! ## matlab's camzoom() is not a simply scaling of camva
+%! clf; sphere()
+%! x = camva ();
+%! camzoom (2)
+%! y = camva ();
+%! ## Matlab 2014a
+%! xm = 10.339584907202;
+%! ym = 5.18033628450948;
+%! assert (x/y, xm/ym, 1e-10)
+
+%!xtest
+%! clf; peaks ()
+%! campos ([20, 30, 50])
+%! camtarget ([0 0 1])
+%! x = camva ();
+%! camzoom (5)
+%! y = camva ();
+%! ## Matlab 2014a
+%! xm = 13.0746680295069;
+%! ym = 2.62588066987212;
+%! assert (x/y, xm/ym, 1e-10)
+
+%!test
+%! ## test ax input by creating another axis
+%! clf
+%! subplot (1, 2, 1); sphere (); hax = gca ();
+%! subplot (1, 2, 2); sphere (); camzoom (hax, 2)
+%! subplot (1, 2, 1); x = camva ();
+%! subplot (1, 2, 2); y = camva ();
+%! assert (x, 5, 2)
+%! assert (y, 10, 2)
+
+%!test
+%! close (hf)
diff -r 6a616814e0c1 -r 990224cf7ea3 scripts/plot/appearance/module.mk
--- a/scripts/plot/appearance/module.mk	Mon Jul 11 19:14:07 2016 -0700
+++ b/scripts/plot/appearance/module.mk	Tue Jul 12 22:19:54 2016 -0700
@@ -13,9 +13,11 @@
   scripts/plot/appearance/axis.m \
   scripts/plot/appearance/box.m \
   scripts/plot/appearance/campos.m \
+  scripts/plot/appearance/camroll.m \
   scripts/plot/appearance/camtarget.m \
   scripts/plot/appearance/camup.m \
   scripts/plot/appearance/camva.m \
+  scripts/plot/appearance/camzoom.m \
   scripts/plot/appearance/caxis.m \
   scripts/plot/appearance/clabel.m \
   scripts/plot/appearance/daspect.m \
