1########################################################################
2##
3## Copyright (C) 2014-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  {} {} rotate (@var{h}, @var{direction}, @var{alpha})
28## @deftypefnx {} {} rotate (@dots{}, @var{origin})
29## Rotate the plot object @var{h} through @var{alpha} degrees around the line
30## with direction @var{direction} and origin @var{origin}.
31##
32## The default value of @var{origin} is the center of the axes object that is
33## the parent of @var{h}.
34##
35## If @var{h} is a vector of handles, they must all have the same parent axes
36## object.
37##
38## Graphics objects that may be rotated are lines, surfaces, patches, and
39## images.
40## @end deftypefn
41
42function rotate (h, direction, alpha, origin)
43
44  ## Note in doc string about compatibility issues with calculation of
45  ## default origin due to possible differences in the auto-scaling
46  ## algorithm between Octave and Matlab.
47
48  if (nargin < 3 || nargin > 4)
49    print_usage ();
50  endif
51
52  is_h = ishghandle (h);
53  if (is_h)
54    ax_list = get (h, "parent");
55    if (iscell (ax_list))
56      ax_list = cell2mat (ax_list);
57    endif
58    if (ax_list == ax_list(1))
59      ax = ax_list(1);
60    else
61       error ("rotate: all handles must be children of the same axes object");
62    endif
63  else
64    error ("rotate: H must be an array of one or more graphics handles");
65  endif
66
67  if (! (isnumeric (direction) && numel (direction) == 3))
68    error ("rotate: invalid direction");
69  endif
70
71  if (! (isnumeric (alpha) && isscalar (alpha)))
72    error ("rotate: invalid rotation angle");
73  endif
74
75  t = get (h, "type");
76
77  is_image = strcmp (t, "image");
78  is_line = strcmp (t, "line");
79  is_patch = strcmp (t, "patch");
80  is_surface = strcmp (t, "surface");
81
82  if (! all (is_image | is_line | is_patch | is_surface))
83    error ("rotate: expecting image, line, patch, or surface objects");
84  endif
85
86  if (nargin == 4)
87    if (! (isnumeric (origin) && numel (origin) == 3))
88       error ("rotate: invalid ORIGIN");
89    endif
90  else
91    ## Should Z limit be considered when computing origin?
92
93    use_zlim = any (is_patch | is_surface);
94
95    if (! use_zlim && any (is_line))
96      idx = find (is_line)';
97      for i = idx
98        if (! isempty (get (h(i), "zdata")))
99          use_zlim = true;
100          break;
101        endif
102      endfor
103    endif
104
105    xlim = get (ax, "xlim");
106    ylim = get (ax, "ylim");
107
108    a = (xlim(1) + xlim(2)) / 2;
109    b = (ylim(1) + ylim(2)) / 2;
110
111    if (use_zlim)
112      zlim = get (ax, "zlim");
113      c = (zlim(1) + zlim(2)) / 2;
114    else
115      c = 0;
116    endif
117
118    origin = [a, b, c];
119  endif
120
121  direction /= norm (direction);
122
123  u = direction(1);
124  v = direction(2);
125  w = direction(3);
126
127  a = origin(1);
128  b = origin(2);
129  c = origin(3);
130
131  sa = sind (alpha);
132  ca = cosd (alpha);
133
134  for i = 1:numel (h)
135    x = get (h(i), "xdata");
136    y = get (h(i), "ydata");
137
138    if (is_image(i))
139      z = zeros (size (x));
140    else
141      z = get (h(i), "zdata");
142      if (isempty (z))
143        z = zeros (size (x));
144      elseif (isvector (x) && isvector (y) && ! isvector (z))
145        [x, y] = meshgrid (x, y);
146      endif
147    endif
148
149    if (a == 0 && b == 0 && c == 0)
150      tmp = (u*x + v*y + w*z) * (1 - ca);
151
152      xr = u*tmp + x*ca + (-w*y + v*z)*sa;
153      yr = v*tmp + y*ca + (w*x - u*z)*sa;
154      zr = w*tmp + z*ca + (-v*x + u*y)*sa;
155    else
156      one_m_ca = 1 - ca;
157      tmp = u*x + v*y + w*z;
158
159      xr = ((a*(v**2 + w**2) - u*(b*v + c*w - tmp))*one_m_ca
160            + x*ca + (-c*v + b*w - w*y + v*z)*sa);
161      yr = ((b*(u**2 + w**2) - v*(a*u + c*w - tmp))*one_m_ca
162            + y*ca + (c*u - a*w + w*x - u*z)*sa);
163      zr = ((c*(u**2 + v**2) - w*(a*u + b*v - tmp))*one_m_ca
164            + z*ca + (-b*u + a*v - v*x + u*y)*sa);
165    endif
166
167    set (h(i), "xdata", xr, "ydata", yr);
168
169    if (! is_image(i))
170      set (h(i), "zdata", zr);
171    endif
172  endfor
173
174endfunction
175
176
177## Test input validation
178%!shared h1, h2, o1, o2, o3
179%! h1 = figure ("visible", "off");
180%! o1 = line ();
181%! h2 = figure ("visible", "off");
182%! o2 = line ();
183%! o3 = text (0, 0, "foobar");
184%!error rotate ()
185%!error rotate (o1)
186%!error rotate (o1, [0,0,0])
187%!error <all handles must be children of the same axes object> rotate ([o1, o2], [0,0,0], 90)
188%!error <invalid direction> rotate (o1, "foo", 90)
189%!error <invalid rotation angle> rotate (o1, [0,0,0], "foo")
190%!error <invalid ORIGIN> rotate (o1, [0,0,0], 90, "foo")
191%!error rotate (o1, [0,0,0], 90, [0,0,0], 1)
192%!error <H must be an array of one or more graphics handles> rotate (NaN, [0,0,0], 90)
193%!error <expecting image, line, patch, or surface objects> rotate (o3, [0,0,0], 90)
194%!test
195%! close (h1);
196%! close (h2);
197