changeset 22288:57fded74ee22

Add camlight function (patch #9014). * scripts/plot/draw/camlight.m: New function. * scripts/plot/draw/private/__rotate_around_axis__.m: New function, split from rotate.m * NEWS: Announce new function. * __unimplemented__.m: Remove camlight from list. * scripts/plot/draw/module.mk: Update build system. * plot.txi: Add docstring to manual.
author Colin Macdonald <cbm@m.fsf.org>
date Mon, 11 Jul 2016 23:31:21 -0700
parents d9913b55ef15
children ed023556d4fa
files NEWS doc/interpreter/plot.txi scripts/help/__unimplemented__.m scripts/plot/draw/camlight.m scripts/plot/draw/module.mk scripts/plot/draw/private/__rotate_around_axis__.m
diffstat 6 files changed, 366 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Sun Aug 14 13:50:13 2016 +0100
+++ b/NEWS	Mon Jul 11 23:31:21 2016 -0700
@@ -72,8 +72,8 @@
     output argument is requested, instead the vector or array of
     interpolated values is always returned for Matlab compatibility.
 
- ** The new function "light" and corresponding graphics object provide
-    light and shadow effects for patch and surface objects.
+ ** The new function "light" and the corresponding graphics object
+    provide light and shadow effects for patch and surface objects.
 
  ** The surfnorm function now returns unnormalized (magnitude != 1)
     normal vectors for compatibility with Matlab.
@@ -110,6 +110,7 @@
  ** Other new functions added in 4.2:
 
       audioformats
+      camlight
       deg2rad
       dialog
       evalc
--- a/doc/interpreter/plot.txi	Sun Aug 14 13:50:13 2016 +0100
+++ b/doc/interpreter/plot.txi	Mon Jul 11 23:31:21 2016 -0700
@@ -402,6 +402,8 @@
 
 @DOCSTRING(material)
 
+@DOCSTRING(camlight)
+
 @DOCSTRING(meshgrid)
 
 @DOCSTRING(ndgrid)
--- a/scripts/help/__unimplemented__.m	Sun Aug 14 13:50:13 2016 +0100
+++ b/scripts/help/__unimplemented__.m	Mon Jul 11 23:31:21 2016 -0700
@@ -569,7 +569,6 @@
   "callSoapService",
   "camdolly",
   "cameratoolbar",
-  "camlight",
   "camlookat",
   "camorbit",
   "campan",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/camlight.m	Mon Jul 11 23:31:21 2016 -0700
@@ -0,0 +1,300 @@
+## 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  {} {} camlight {}
+## @deftypefnx {} {} camlight right
+## @deftypefnx {} {} camlight left
+## @deftypefnx {} {} camlight headlight
+## @deftypefnx {} {} camlight (@var{az}, @var{el})
+## @deftypefnx {} {} camlight (@dots{}, @var{style})
+## @deftypefnx {} {} camlight (@var{hl}, @dots{})
+## @deftypefnx {} {@var{h} =} camlight (@dots{})
+## Add a light object to a figure using a simple interface.
+##
+## When called with no arguments, a light object is added to the current plot
+## and is placed slightly above and to the right of the camera's current
+## position: this is equivalent to @code{camlight right}.  The commands
+## @code{camlight left} and @code{camlight headlight} behave similarly with
+## the placement being either left of the camera position or centered on the
+## camera position.
+##
+## For more control, the light position can be specified by an azimuthal
+## rotation @var{az} and an elevation angle @var{el}, both in degrees,
+## relative to the current properties of the camera.
+##
+## The optional string @var{style} specifies whether the light is a local point
+## source (@qcode{"local"}, the default) or placed at infinite distance
+## (@qcode{"infinite"}).
+##
+## If the first argument @var{hl} is a handle to a light object, then act on
+## this light object rather than creating a new object.
+##
+## The optional return value @var{h} is a graphics handle to the light object.
+## This can be used to move or further change properties of the light object.
+##
+## Examples:
+##
+## Add a light object to a plot
+##
+## @example
+## @group
+## @c doctest: +SKIP
+## sphere (36);
+## camlight
+## @end group
+## @end example
+##
+## Position the light source exactly
+##
+## @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.
+##
+## Return a handle to further manipulate the light object
+##
+## @example
+## @group
+## @c doctest: +SKIP
+## clf
+## sphere (36);
+## hl = camlight ("left");
+## set (hl, "color", "r");
+## @end group
+## @end example
+##
+## @seealso{light}
+## @end deftypefn
+
+function h = camlight (varargin)
+
+  if (nargin > 4)
+    print_usage ();
+  endif
+
+  ## Note: There is a very small chance of a collision between a numeric double
+  ## specifying azimuth and a light handle object (also a numeric double).
+  ## We don't worry about that.
+  if (numel (varargin) > 0 && numel (varargin{1}) == 1
+      && ishandle (varargin{1}))
+    hl = varargin{1};
+    if (! ishghandle (hl, "light"))
+      error ("camlight: HL must be a handle to a light object");
+    endif
+    varargin(1) = [];
+    nargin = nargin - 1;
+  else
+    hl = [];
+  endif
+
+  style = "local";
+  where = "";
+
+  if (nargin == 0)
+    where = "right";
+
+  elseif (nargin == 1 && ischar (varargin{1}))
+    arg1 = varargin{1};
+    if (strcmpi (arg1, "local") || strcmpi (arg1, "infinite"))
+      style = arg1;
+      where = "right";
+    else
+      where = arg1;
+    endif
+
+  elseif (nargin == 2)
+    arg1 = varargin{1};
+    arg2 = varargin{2};
+    if (isnumeric (arg1) && isscalar (arg1)
+        && isnumeric (arg2) && isscalar (arg2))
+      az = arg1;
+      el = arg2;
+    elseif (ischar (arg1) && ischar (arg2))
+      where = arg1;
+      style = arg2;
+    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 (! isempty (where))
+    switch (tolower (where))
+      case "left"
+        az = -30;
+        el = 30;
+
+      case "right"
+        az = 30;
+        el = 30;
+
+      case "headlight"
+        az = 0;
+        el = 0;
+
+      otherwise
+        error ("camlight: invalid light position '%s'", where);
+
+    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 /= 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 (hl))
+    hl = light ("Position", pos, "style", style);
+  else
+    set (hl, "Position", pos, "style", style);
+  endif
+
+  if (nargout > 0)
+    h = hl;
+  endif
+
+endfunction
+
+
+%!demo
+%! ## Adding lights to a scene
+%! sphere (64);
+%! camlight
+%!
+%! ## Add a second 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 ();
+%!   pause (0.01);
+%! end
+
+%!demo
+%! sphere (48);
+%! title ("Move the camera and update the light position");
+%! axis equal;  shading flat
+%! view (30, 30);
+%!
+%! hl = camlight ();          # keep a handle to the light
+%!
+%! for a = 30:2:390
+%!   view (a, 30);
+%!   camlight (hl, "right");  # update light position
+%!   drawnow ();
+%!   pause (0.01);
+%! end
+
+%!test
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   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);
+%!
+%!   h = camlight (300, -190);
+%!   A = get (h, "position");
+%!   B = [-11.054849015640563 2.931330143100648 -11.315623892092518];
+%!   assert (A, B, -20*eps);
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
+
+## Move a light
+%!test
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   sphere ();
+%!   h1 = camlight ("left");
+%!   h2 = camlight ();
+%!   p2 = get (h2, "position");
+%!   camlight (h1, "right")
+%!   p1 = get (h1, "position");
+%!   assert (p1, p2);
+%!   camlight (h1);
+%!   p1 = get (h1, "position");
+%!   assert (p1, p2);
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
+
+## Updating style also moves light
+%!test
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   sphere ();
+%!   h1 = camlight ("right");
+%!   h  = camlight ("headlight");
+%!   p1 = get (h1, "position");
+%!   h2 = camlight (h, "local");
+%!   p2 = get (h2, "position");
+%!   assert (h, h2);
+%!   assert (p1, p2);
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
+
+## Test input validation
+%!error camlight (1,2,3,4,5)
+%!error <HL must be a handle to a light object> camlight (0, "left")
+%!error <Invalid call> camlight ({1}, {2})
+%!error <Invalid call> camlight (rand (), 1, 2, 3)
+%!error <invalid light position 'foobar'> camlight ("foobar")
+%!error <invalid light position 'foobar'> camlight ("foobar", "local")
+
--- a/scripts/plot/draw/module.mk	Sun Aug 14 13:50:13 2016 +0100
+++ b/scripts/plot/draw/module.mk	Mon Jul 11 23:31:21 2016 -0700
@@ -16,6 +16,7 @@
   scripts/plot/draw/private/__pie__.m \
   scripts/plot/draw/private/__plt__.m \
   scripts/plot/draw/private/__quiver__.m \
+  scripts/plot/draw/__rotate_around_axis__.m \
   scripts/plot/draw/private/__scatter__.m \
   scripts/plot/draw/private/__stem__.m \
   scripts/plot/draw/private/__unite_shared_vertices__.m
@@ -24,6 +25,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 \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/private/__rotate_around_axis__.m	Mon Jul 11 23:31:21 2016 -0700
@@ -0,0 +1,59 @@
+## 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})
+## Rotate the points given by X, Y, Z about an axis by ANGLE degrees.
+## The axis is specified by the vector DIR and the point ORIGIN.
+## @end deftypefn
+
+function [xr, yr, zr] = __rotate_around_axis__ (x, y, z, angle, dir, origin);
+
+  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
+