##  Copyright (C) 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{d} =} ls_distance_fcn (@var{phi}, @var{h} = 1)
## 
## Calculate the distance function of a set.  The domain is described by
## its level-set function, whose values on a rectangular grid with spacing
## @var{h} are given in the array @var{phi}.
##
## In contrast to @code{ls_signed_distance}, which calculates the
## @emph{signed} distance function, @var{d} will be set to zero
## on interior points.  This allows @code{ls_distance_fcn} to be
## faster, as only exterior points are processed.
##
## 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_signed_distance, ls_normalise}
## @end deftypefn

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

  d = __levelset_internal_init_narrowband (phi, h);
  d(ls_inside (phi)) = 0;

  f = ones (size (phi));
  d = fastmarching (d, h * f);

  assert (all (d(:) >= 0));
endfunction

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

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

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

% 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_distance_fcn (phi, h);
%!  assert (d, max (phi, 0), 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_distance_fcn (phi, h);
%!  assert (d, max (sqrt (RRsq) - 1, 0), h);

% Compare timing to ls_signed_distance.
%!test
%!  n = 500;
%!  x = linspace (-10, 10, n);
%!  h = x(2) - x(1);
%!
%!  [XX, YY] = meshgrid (x, x);
%!  phi = ls_genbasic (XX, YY, "sphere", [0, 0], 8);
%!
%!  id = tic ();
%!  d = ls_distance_fcn (phi, h);
%!  time1 = toc (id);
%!
%!  id = tic ();
%!  sd = ls_signed_distance (phi, h);
%!  time2 = toc (id);
%!
%!  assert (d, max (sd, 0), sqrt (eps));
%!  printf ("ls_distance_fcn faster than ls_signed_distance by %.1f%%\n", ...
%!          100 * ((time2 - time1) / time2));
%!  assert (time1 < time2);

% Check that ls_distance_fcn returns the same as ls_signed_distance.
%!test
%!  phis = ls_get_tests ();
%!  for i = 1 : length (phis)
%!    phi = ls_normalise (phis{i});
%!    d = ls_distance_fcn (phi);
%!    sd = ls_signed_distance (phi);
%!    assert (d, max (sd, 0), sqrt (eps));
%!  endfor

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

% Calculate distance from an ellipsoid and plot it.
%!demo
%!  n = 100;
%!  x = linspace (-10, 10, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi = ls_genbasic (XX, YY, "sphere", [0, 0], [8, 5]);
%!  d = ls_distance_fcn (phi, h);
%!
%!  figure ();
%!  mesh (XX, YY, d);
%!  view ([45, 45]);
