##  Copyright (C) 2014-2015  Daniel Kraft <d@domob.eu>
##  GNU Octave level-set package.
##
##  This program 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 program 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 program.  If not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {Function File} {@var{phi} =} ls_genbasic (@var{XX}, @var{YY}, @var{shape}, ...)
## @deftypefnx {Function File} {@var{phi} =} ls_genbasic (@var{XX}, ..., @var{shape}, ...)
## @deftypefnx {Function File} {@var{phi} =} ls_genbasic (..., @qcode{"sphere"}, @var{c}, @var{r})
## @deftypefnx {Function File} {@var{phi} =} ls_genbasic (..., @qcode{"box"}, @var{a}, @var{b})
## @deftypefnx {Function File} {@var{phi} =} ls_genbasic (..., @qcode{"half"}, @var{p}, @var{n})
## 
## Construct the level-set function for a basic geometric shape.  The arguments
## @var{XX} and @var{YY} should be the coordinates where the level-set function
## should be evaluated.  For a case that is not 2D, the respective
## number of arguments must be given.
##
## @var{shape} specifies what shape should be constructed, the arguments
## following are shape-dependent.
## Possible shapes:
##
## @table @asis
## @item @qcode{"sphere"}
## Construct a sphere or ellipsoid with centre @var{c} and radius @var{r}.
## Optionally, @var{r} can also be a vector giving the radii in each
## coordinate direction separately.
##
## @item @qcode{"box"}
## Construct a box spanned by the given two vertex coordinates.
##
## @item @qcode{"half"}
## Construct a half-space at one side of a hyperplane.  The plane
## is specified by some point @var{p} on it, and the normal vector
## @var{n} pointing @emph{into} the domain.
## @end table
##
## @seealso{ls_union, ls_intersect, ls_setdiff, meshgrid, ndgrid}
## @end deftypefn

function phi = ls_genbasic (varargin)

  % Read in the coordinate arguments until we hit a string (should be the
  % shape).  Check that they have matching dimension.
  coords = {};
  argpos = 1;
  while (true)
    if (argpos > length (varargin))
      print_usage ();
    endif

    cur = varargin{argpos++};
    if (strcmp (typeinfo (cur), "string"))
      shape = cur;
      break;
    endif

    if (length (coords) == 0)
      sz = size (cur);
    else
      if (~all (size (cur) == sz))
        error ("coordinate grid size mismatch");
      endif
    endif
    coords{end + 1} = cur;
  endwhile
  if (length (coords) == 0)
    print_usage ();
  endif

  % For ease-of-use later, extract the shape-dependent arguments now.
  shargs = cell (1, length (varargin) - argpos + 1);
  for i = 1 : length (shargs)
    shargs{i} = varargin{argpos + i - 1};
  endfor

  % Handle the actual shapes themselves.
  switch (shape)
    case "sphere"
      phi = construct_sphere (coords, shargs);
    case "box"
      phi = construct_box (coords, shargs);
    case "half"
      phi = construct_half (coords, shargs);
    otherwise
      error ("invalid value '%s' for SHAPE argument", shape);
  endswitch
endfunction

% Construct a sphere.
function phi = construct_sphere (coords, shargs)
  if (length (shargs) ~= 2)
    print_usage ("ls_genbasic");
  endif
  c = shargs{1};
  r = shargs{2};
  dims = length (coords);
  if (length (c) ~= dims)
    error ("wrong number of dimensions for centre");
  endif
  if (length (r) == 1)
    r = repmat (r, 1, dims);
  endif
  if (length (r) ~= dims)
    error ("wrong number of dimensions for radii");
  endif

  % The level-set function will simply use the standard equation of
  % an ellipse:
  %   X^2 / r1^2 + Y^2 / r2^2 = 1^2

  rsq = zeros (size (coords{1}));
  for i = 1 : dims
    rsq += ((coords{i} - c(i)) / r(i)).^2;
  endfor

  phi = rsq - 1;
endfunction

% Construct a box.
function phi = construct_box (coords, shargs)
  if (length (shargs) ~= 2)
    print_usage ("ls_genbasic");
  endif
  a = shargs{1};
  b = shargs{2};
  dims = length (coords);
  if (length (a) ~= dims || length (b) ~= dims)
    error ("wrong number of dimensions for box vertex");
  endif

  % Start with the full domain.  For each dimension, construct
  % a "slice" that checks the box constraint for this dimension only,
  % and intersect all those.

  phi = -ones (size (coords{1}));
  for i = 1 : dims
    m = (a(i) + b(i)) / 2;
    r = abs (a(i) - b(i)) / 2;
    inner = abs (coords{i} - m) - r;

    phi = ls_intersect (phi, inner);
  endfor
endfunction

% Construct a half-space.
function phi = construct_half (coords, shargs)
  if (length (shargs) ~= 2)
    print_usage ("ls_genbasic");
  endif
  p = shargs{1};
  n = shargs{2};
  dims = length (coords);
  if (length (p) ~= dims)
    error ("wrong number of dimensions for plane point");
  endif
  if (length (n) ~= dims)
    error ("wrong number of dimensions for normal vector");
  endif

  % We use the plane equation in normal form.  That is, for a point x to
  % be in the domain, the inner product between (x - p) and n must be
  % positive.  Use its negative as level-set function.

  phi = zeros (size (coords{1}));
  for i = 1 : dims
    phi -= n(i) * (coords{i} - p(i));
  endfor
endfunction

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Tests.

% Some basic variables.
%!shared x, y, XX, YY, XXX, YYY, ZZZ
%!  n = 121;
%!  x = linspace (-5, 7, n);
%!  y = linspace (-7, 5, n);
%!  [XX, YY] = meshgrid (x, y);
%!  [XXX, YYY, ZZZ] = ndgrid (x, y, x);

% Test for basic (common) argument parsing.
%!error <Invalid call to>
%!  ls_genbasic ();
%!error <Invalid call to>
%!  ls_genbasic (1, 2);
%!error <Invalid call to>
%!  ls_genbasic ("sphere");
%!error <invalid value 'unknown' for SHAPE argument>
%!  ls_genbasic (XX, YY, "unknown");
%!test
%!  ls_genbasic (x, "sphere", 0, 1);
%!  ls_genbasic (x', "sphere", 0, 1);
%!  ls_genbasic (XX, YY, "sphere", [0, 0], 1);
%!  ls_genbasic (0, 1, "sphere", [0, 0], 1);

% Test sphere construction.
%!error <Invalid call to>
%!  ls_genbasic (XX, YY, "sphere");
%!error <Invalid call to>
%!  ls_genbasic (XX, YY, "sphere", 1);
%!error <Invalid call to>
%!  ls_genbasic (XX, YY, "sphere", 1, 2, 3);
%!error <wrong number of dimensions for centre>
%!  ls_genbasic (XX, YY, "sphere", [1, 2, 3], 1);
%!error <wrong number of dimensions for radii>
%!  ls_genbasic (XX, YY, "sphere", [1, 2], [1, 2, 3]);
%!test
%!  assert (ls_genbasic (XX, YY, "sphere", [1, -1], 1), ...
%!          ls_genbasic (XX, YY, "sphere", [1, -1], [1, 1]));
%!test
%!  c = [1; -1; 2];
%!  r = [1; 2; 0.6];
%!  tol = 0.4;
%!  phi1 = ls_genbasic (XXX, YYY, ZZZ, "sphere", c, r);
%!  phi2 = ls_genbasic (XXX, YYY, ZZZ, "sphere", c, min (r) - tol);
%!  assert (ls_issubset (phi2, phi1));
%!  coords = {XXX, YYY, ZZZ};
%!  for i = 1 : length (c)
%!    assert (ls_disjoint (phi1, ...
%!                         coords{i} - (c(i) - r(i)), ...
%!                         c(i) + r(i) - coords{i}));
%!    assert (~ls_disjoint (phi1 - tol, coords{i} - (c(i) - r(i))));
%!    assert (~ls_disjoint (phi1 - tol, c(i) + r(i) - coords{i}));
%!  endfor

% Test box construction.
%!error <Invalid call to>
%!  ls_genbasic (XX, YY, "box", 1);
%!error <Invalid call to>
%!  ls_genbasic (XX, YY, "box", 1, 2, 3);
%!error <wrong number of dimensions for box vertex>
%!  ls_genbasic (XX, YY, "box", [1, 2, 3], [1, 2]);
%!error <wrong number of dimensions for box vertex>
%!  ls_genbasic (XX, YY, "box", [1; 2], [1, 2, 3]);
%!test
%!  assert (ls_equal (ls_genbasic (x, "box", -1, 2), abs (x - 0.5) - 1.5));
%!  assert (ls_equal (ls_genbasic (x, "box", 2, -1), abs (x - 0.5) - 1.5));
%!test
%!  a = [2; -3; 1];
%!  b = [0; 2; -1];
%!  tol = 0.2;
%!  phi1 = ls_genbasic (XXX, YYY, ZZZ, "box", a, b);
%!  phi2 = ls_genbasic (XXX, YYY, ZZZ, "box", b, a);
%!  assert (phi1, phi2);
%!  coords = {XXX, YYY, ZZZ};
%!  for i = 1 : length (coords)
%!    assert (ls_disjoint (phi1, ...
%!                         coords{i} - min (a(i), b(i)), ...
%!                         max (a(i), b(i)) - coords{i}));
%!    assert (~ls_disjoint (phi1 - tol, coords{i} - min (a(i), b(i))));
%!    assert (~ls_disjoint (phi1 - tol, max (a(i), b(i)) - coords{i}));
%!  endfor

% Test half-space construction.
%!error <Invalid call to>
%!  ls_genbasic (XX, YY, "half", 1);
%!error <Invalid call to>
%!  ls_genbasic (XX, YY, "half", 1, 2, 3);
%!error <wrong number of dimensions for plane point>
%!  ls_genbasic (XX, YY, "half", [1, 2, 3], [1, 2]);
%!error <wrong number of dimensions for normal vector>
%!  ls_genbasic (XX, YY, "half", [1; 2], [1, 2, 3]);
%!test
%!  p = [0, 2, 1];
%!
%!  phi = ls_genbasic (XXX, YYY, ZZZ, "half", p, [0; 0; 1]);
%!  assert (ls_equal (phi, 1 - ZZZ));
%!
%!  normal = [1; 2; -0.5];
%!  phi1 = ls_genbasic (XXX, YYY, ZZZ, "half", p, normal);
%!  phi2 = ls_genbasic (XXX, YYY, ZZZ, "half", p, 2 * normal);
%!  assert (ls_equal (phi1, phi2));
%!
%!  phi = ls_genbasic (XX, YY, "half", [0, 0], [1, -1]);
%!  assert (ls_equal (phi, YY - XX));

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Demos.

%!demo
%!  n = 200;
%!  x = linspace (-7, 7, n);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi1 = ls_genbasic (XX, YY, "box", [-5, -3], [3, 6]);
%!  phi2 = ls_genbasic (XX, YY, "sphere", [-1; 1], 2);
%!  phi3 = ls_genbasic (XX, YY, "sphere", [0; 0], [6, 1]);
%!  phi4 = ls_genbasic (XX, YY, "half", [3, -3], [1, -2]) / 8;
%!
%!  phi = ls_union (ls_setdiff (phi1, phi2), phi3, phi4);
%!  figure ();
%!  hold ("on");
%!  imagesc (x, x, phi);
%!  set (gca (), "ydir", "normal");
%!  ls_sign_colourmap ();
%!  contour (XX, YY, phi, [0, 0], "k");
%!  hold ("off");
%!  axis ("equal");
