##  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_init_narrowband (@var{phi}, @var{h} = 1)
## 
## Calculate the estimated distances of grid points in the ``narrow band''
## to the zero level-set of @var{phi}.  The points considered are those
## which have neighbours with different sign of @var{phi}.  The calculated
## distances are returned in @var{d}, where values for points not in the
## narrow band are set to @code{NA}.
## If the optional argument @var{h} is given, it is used as the grid spacing
## instead of the default value of 1.
##
## The distances will be positive or negative depending on the sign
## of @var{phi}.  This means that @var{d} gives the signed
## distance function of the level-set domain for narrow-band points.
##
## We assume a linear model for @var{phi}, meaning that the approximate
## intersection points on grid edges are calculated using linear interpolation.
## The distances at narrow-band points are calculated using the quadratic
## update equation of the Fast-Marching Method using these approximated
## intersection points.
##
## Note that this method does not take an arbitrary speed field into account.
## It assumes a uniform speed of 1 everywhere.  For different speeds, the
## resulting distances must be scaled as required.
##
## 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{ls_signed_distance, ls_nb_from_geom, ls_normalise}
## @end deftypefn

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

  d = __levelset_internal_init_narrowband (phi, h);
  d = ls_copy_sign (d, phi);
endfunction

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

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

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

% 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_init_narrowband (phi, h);
%!  where = ~isna (d);
%!  assert (d(where), phi(where), 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_init_narrowband (phi, h);
%!  where = ~isna (d);
%!  assert (d(where), sqrt (RRsq(where)) - 1, h / 3);

% Test cases of infinities in level-set function.
%!test
%!  tol = sqrt (eps);
%!  assert (ls_init_narrowband ([Inf, -Inf, Inf]), [0.5, -0.5, 0.5], tol);
%!  assert (ls_init_narrowband ([Inf, -1, Inf]), [1, 0, 1], tol);
%!  %assert (ls_init_narrowband ([1, -Inf, 1]), [0, 1, 0], tol);

% Compare two contour lines as per contourc for their Hausdorff distance.
% This is used to check that the contour lines found for d and phi itself
% are (almost) the same.
%
%!function dh = distToSegment (x, a, b)
%!  dir = b - a;
%!  dir /= norm (dir);
%!  proj = dot (x - a, dir);
%!  if (proj < 0)
%!    dh = norm (x - a);
%!  elseif (proj > 1)
%!    dh = norm (x - b);
%!  else
%!    dh = norm (x - (a + proj * dir));
%!  endif
%!endfunction
%
%!function dh = distToLine (x, c)
%!  assert (c(1, 1), 0);
%!  dh = Inf;
%!  for i = 2 : size (c, 2) - 1
%!    dh = min (dh, distToSegment (x, c(:, i), c(:, i + 1)));
%!  endfor
%!endfunction
%
%!function dh = distOfContoursAsym (c1, c2)
%!  dh = 0;
%!  for i = 2 : size (c1, 2)
%!    dh = max (dh, distToLine (c1(:, i), c2));
%!  endfor
%!endfunction
%
%!function dh = distOfContours (c1, c2)
%!  dh = max (distOfContoursAsym (c1, c2), distOfContoursAsym (c2, c1));
%!endfunction

% Compare distance of contour lines for the test phis.
%!test
%!  phis = ls_get_tests ();
%!  printf ("Comparing contours for %d cases...\n", length (phis));
%!  for i = 1 : length (phis)
%!    d = ls_init_narrowband (phis{i});
%!    c1 = contourc (d, [0, 0]);
%!    if (size (c1, 2) > 0)
%!      c2 = contourc (phis{i}, [0, 0]);
%!      dh = distOfContours (c1, c2);
%!      printf ("i = %d: dist = %f\n", i, dh);
%!      if (isfinite (dh))
%!        assert (dh, 0, 1e-1);
%!      endif
%!    endif
%!  endfor
