##  Copyright (C) 2014  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{d} =} ls_signed_distance (@var{phi}, @var{h} = 1)
## 
## Calculate the signed distance function of a set described by its level-set
## function.  The array @var{phi} must contain the values of the level-set
## function on a rectangular grid with spacing @var{h}.
##
## The initial distances are approximated using @code{ls_init_narrowband},
## and then @code{fastmarching} is used to propagate them to all other points
## on the grid.
##
## It may be a good idea to use @code{ls_normalise} on the level-set function
## before using this method, to prevent almost-zero values from underflowing
## due to the performed calculations.
##
## @seealso{fastmarching, ls_distance_fcn, ls_solve_stationary, ls_normalise}
## @end deftypefn

function d = ls_signed_distance (phi, h = 1)
  if (nargin () < 1 || nargin () > 2)
    print_usage ();
  endif

  % We work with all positive distances for the beginning.  This does not
  % hurt, as points in the interior can only be reached by points on the
  % inner side of the boundary, and vice versa.
  d = __levelset_internal_init_narrowband (phi, h);
  f = ones (size (phi));
  d = fastmarching (d, h * f);

  % Fix the sign.
  d = ls_copy_sign (d, phi);
endfunction

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

% Test for error conditions.
%!error <Invalid call to>
%!  ls_signed_distance ()
%!error <Invalid call to>
%!  ls_signed_distance (1, 2, 3)

% Check 0D case.
%!test
%!  assert (ls_signed_distance ([]), []);

% Check 1D case, for which the expected result is trivial to calculate
% and should be almost exact.
%!test
%!  n = 10;
%!  x = linspace (-2, 2, n);
%!  h = x(2) - x(1);
%!  phi = abs (x) - 1;
%!
%!  d = ls_signed_distance (phi, h);
%!  assert (d, phi, sqrt (eps));

% Test with circular region in 3D.
%!test
%!  n = 50;
%!  x = linspace (-2, 2, n);
%!  h = x(2) - x(1);
%!  [XX, YY, ZZ] = ndgrid (x);
%!  RRsq = XX.^2 + YY.^2 + ZZ.^2;
%!  phi = RRsq - 1;
%!
%!  d = ls_signed_distance (phi, h);
%!  assert (d, sqrt (RRsq) - 1, h);

% Ensure that a previous error with numeric underflow for very small but
% non-zero phi values is now fixed.
%!test
%!  phi = [5e-6, 1.7e-154, -1.3e-2];
%!  for h = [0.02, 0.01, 0.005]
%!    d = ls_signed_distance (phi, h);
%!    assert (all (isfinite (d)));
%!    assert (d(2), 0);
%!  endfor

% Check that ls_signed_distance together with phi normalisation
% produces a level-set domain equivalent to the original one.
%!test
%!  phis = ls_get_tests ();
%!  for i = 1 : length (phis)
%!    phi = ls_normalise (phis{i});
%!    d = ls_signed_distance (phi);
%!    assert (ls_equal (d, phi));
%!  endfor

% Check for proper handling of signed zeros.
%!test
%!  if (exist ("signbit") == 5)
%!    phi = [-0, 0];
%!    d = ls_signed_distance (phi);
%!    assert (ls_equal (phi, d));
%!  else
%!    warning ("'signbit' function not available, skipping test.");
%!  endif

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

% Use ls_signed_distance to "normalise" a very irregularly scaled
% initial level-set function.
%!demo
%!  n = 100;
%!  x = linspace (-10, 10, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi0 = atan (ls_union ((XX.^2 + (YY - 5).^2) - 20, ...
%!                         (XX.^2 + (YY + 5).^2) - 20));
%!  %phi0 = XX.^2 / 1.3^2 + YY.^2 - 1;
%!  d = ls_signed_distance (phi0, h);
%!
%!  figure ();
%!  subplot (1, 2, 1);
%!  contour (XX, YY, phi0);
%!  title ("Initial");
%!  colorbar ();
%!  axis ("equal");
%!
%!  subplot (1, 2, 2);
%!  contour (XX, YY, d);
%!  title ("Signed Distance");
%!  colorbar ();
%!  axis ("equal");
