view scripts/plot/appearance/camorbit.m @ 28240:2fb684dc2ec2

axis.m: Implement "fill" option for Matlab compatibility. * axis.m: Document that "fill" is a synonym for "normal". Place "vis3d" option in documentation table for modes which affect aspect ratio. Add strcmpi (opt, "fill") to decode opt and executed the same behavior as "normal".
author Rik <rik@octave.org>
date Fri, 24 Apr 2020 13:16:09 -0700
parents bd51beb6205e
children 89a425f2c202 0a5b15007766
line wrap: on
line source

########################################################################
##
## Copyright (C) 2016-2020 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## 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.
##
## Octave 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 Octave; see the file COPYING.  If not, see
## <https://www.gnu.org/licenses/>.
##
########################################################################

## -*- texinfo -*-
## @deftypefn  {} {} camorbit (@var{theta}, @var{phi})
## @deftypefnx {} {} camorbit (@var{theta}, @var{phi}, @var{coorsys})
## @deftypefnx {} {} camorbit (@var{theta}, @var{phi}, @var{coorsys}, @var{dir})
## @deftypefnx {} {} camorbit (@var{theta}, @var{phi}, "data")
## @deftypefnx {} {} camorbit (@var{theta}, @var{phi}, "data", "z")
## @deftypefnx {} {} camorbit (@var{theta}, @var{phi}, "data", "x")
## @deftypefnx {} {} camorbit (@var{theta}, @var{phi}, "data", "y")
## @deftypefnx {} {} camorbit (@var{theta}, @var{phi}, "data", [@var{x} @var{y} @var{z}])
## @deftypefnx {} {} camorbit (@var{theta}, @var{phi}, "camera")
## @deftypefnx {} {} camorbit (@var{hax}, @dots{})
## Rotate the camera up/down and left/right around its target.
##
## Move the camera @var{phi} degrees up and @var{theta} degrees to the right,
## as if it were in an orbit around its target.
## Example:
##
## @example
## @group
## @c doctest: +SKIP
## sphere ()
## camorbit (30, 20)
## @end group
## @end example
##
## These rotations are centered around the camera target
## (@pxref{XREFcamtarget,,camtarget}).
## First the camera position is pitched up or down by rotating it @var{phi}
## degrees around an axis orthogonal to both the viewing direction
## (specifically @code{camtarget() - campos()}) and the camera ``up vector''
## (@pxref{XREFcamup,,camup}).
## Example:
##
## @example
## @group
## @c doctest: +SKIP
## camorbit (0, 20)
## @end group
## @end example
##
## The second rotation depends on the coordinate system @var{coorsys} and
## direction @var{dir} inputs.
## The default for @var{coorsys} is @qcode{"data"}.  In this case, the camera
## is yawed left or right by rotating it @var{theta} degrees around an axis
## specified by @var{dir}.
## The default for @var{dir} is @qcode{"z"}, corresponding to the vector
## @code{[0, 0, 1]}.
## Example:
##
## @example
## @group
## @c doctest: +SKIP
## camorbit (30, 0)
## @end group
## @end example
##
## When @var{coorsys} is set to @qcode{"camera"}, the camera is moved left or
## right by rotating it around an axis parallel to the camera up vector
## (@pxref{XREFcamup,,camup}).
## The input @var{dir} should not be specified in this case.
## Example:
##
## @example
## @group
## @c doctest: +SKIP
## camorbit (30, 0, "camera")
## @end group
## @end example
##
## (Note: the rotation by @var{phi} is unaffected by @qcode{"camera"}.)
##
## The @code{camorbit} command modifies two camera properties:
## @ref{XREFcampos,,campos} and @ref{XREFcamup,,camup}.
##
## By default, this command affects the current axis; alternatively, an axis
## can be specified by the optional argument @var{hax}.
##
## @seealso{camzoom, camroll, camlookat}
## @end deftypefn


function camorbit (varargin)

  [hax, varargin, nargin] = __plt_get_axis_arg__ ("camorbit", varargin{:});

  if (nargin < 2 || nargin > 4)
    print_usage ();
  endif

  theta = varargin{1};
  phi = varargin{2};
  if (! (isnumeric (theta) && isscalar (theta)
         && isnumeric (phi) && isscalar (phi)))
    error ("camorbit: THETA and PHI must be numeric scalars")
  endif

  if (nargin < 3)
    coorsys = "data";
  else
    coorsys = varargin{3};
    if (! any (strcmpi (coorsys, {"data" "camera"})))
      error ("camorbit: COORSYS must be 'data' or 'camera'")
    endif
  endif

  if (nargin < 4)
    dir = "z";
  else
    if (strcmpi (coorsys, "camera"))
      error ("camorbit: DIR must not be used with 'camera' COORSYS.");
    endif
    dir = varargin{4};
  endif

  if (ischar (dir))
    switch tolower (dir)
      case "x"
        dir = [1 0 0];
      case "y"
        dir = [0 1 0];
      case "z"
        dir = [0 0 1];
      otherwise
        error ("camorbit: DIR must be 'x', 'y', 'z' or a numeric 3-element \
                vector.");
    endswitch
  endif
  if (! (isnumeric (dir) && numel (dir) == 3))
    error ("camorbit: DIR must be 'x', 'y', 'z' or a numeric 3-element \
            vector.");
  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);
  pitch_ax = cross (up, view_ax);

  if (strcmpi (coorsys, "camera"))
    yaw_ax = up;
  else
    yaw_ax = dir;
  end

  ## First pitch up then yaw right (order matters)
  pos = num2cell (campos (hax));
  [pos{:}] = __rotate_around_axis__ (pos{:}, phi, pitch_ax, camtarget (hax));
  [pos{:}] = __rotate_around_axis__ (pos{:}, theta, yaw_ax, camtarget (hax));
  pos = [pos{:}];

  up = num2cell (up);
  [up{:}] = __rotate_around_axis__ (up{:}, phi, pitch_ax, [0 0 0]);
  [up{:}] = __rotate_around_axis__ (up{:}, theta, yaw_ax, [0 0 0]);
  up = [up{:}];

  camup (hax, up)
  campos (hax, pos)

endfunction


%!demo
%! clf;
%! peaks ();
%! title ("camorbit() demo #1");
%! ## rotate the camera upwards
%! camorbit (0, 30);
%! ## rotate the camera right
%! camorbit (20, 0);


%!test
%! hf = figure ("visible", "off");
%! unwind_protect
%!   sphere ();
%!   campos ([20 0 0]);
%!   camorbit (0, 60);
%!   p = campos ();
%!   u = camup ();
%!   assert (p, [10 0 sqrt(3)*10], -eps);
%!   assert (u, [-sqrt(3)/2 0 0.5], -eps);
%! unwind_protect_cleanup
%!   close (hf);
%! end_unwind_protect

%!test
%! hf = figure ("visible", "off");
%! unwind_protect
%!   sphere ();
%!   camorbit(20, 30, "camera")
%!   p = campos ();
%!   u = camup ();
%!   ## Matlab 2008a
%!   pm = [-0.72497293219048453 -9.3722459659600944 14.547694655894309];
%!   um = [ 0.37563433931679546 0.77045096344496944 0.51507684480352300];
%!   assert (p, pm, -5e-15);
%!   assert (u, um, -5e-15);
%! unwind_protect_cleanup
%!   close (hf);
%! end_unwind_protect

%!test
%! hf = figure ("visible", "off");
%! unwind_protect
%!   sphere ();
%!   camorbit(20, 30, "data", [1 2 3]);
%!   p = campos ();
%!   u = camup ();
%!   ## Matlab 2014a
%!   pm = [-0.21577267252509916 -9.0492661542881496 14.766997806685227];
%!   um = [ 0.41305819997282633 0.77380119822661142 0.48022351989284007];
%!   assert (p, pm, -2e-14);  # FIXME: looser tolerance needed on i386
%!   assert (u, um, -5e-15);
%! unwind_protect_cleanup
%!   close (hf);
%! end_unwind_protect

%!test
%! hf = figure ("visible", "off");
%! unwind_protect
%!   sphere ();
%!   camorbit (54, 37);
%!   p = campos ();
%!   u = camup ();
%!   va = camva ();
%!   ## Matlab 2016a
%!   pm = [ 1.92211976102821500 -6.48896756467585330 15.943611747933700];
%!   um = [-0.26143750325492854  0.88259821953215356 0.39073112848927383];
%!   vam = 10.127485041473481;
%!   assert (p, pm, -5e-15);
%!   assert (u, um, -5e-15);
%!   assert (va, vam, -5e-15);
%! unwind_protect_cleanup
%!   close (hf);
%! end_unwind_protect

## another figure, test hax
%!test
%! hf = figure ("visible", "off");
%! unwind_protect
%!   hax = subplot (1, 2, 1);
%!   sphere (hax);
%!   x = campos ();
%!   camorbit (20, 30)
%!   subplot (1, 2, 2);
%!   sphere ();
%!   camorbit (hax, -20, -30)
%!   y = campos (hax);
%!   assert (x, y, -eps);
%! unwind_protect_cleanup
%!   close (hf);
%! end_unwind_protect

## Test input validation
%!error <numeric scalars> camorbit ([1 2], [3 4])
%!error <Invalid call> camorbit (1, 2, "data", "z", 42)
%!error <DIR must be> camorbit (1, 2, "data", "meh")
%!error <DIR must be> camorbit (1, 2, "data", [1 2 3 4])
%!error <DIR must not be> camorbit (1, 2, "camera", "x")
%!error <COORSYS must be> camorbit (1, 2, "meh")