##  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{mesh}, @var{vmap}] =} ls_build_mesh (@var{geom}, @var{phi}, @var{glob} = false)
##
## Build a triangle mesh for the level-set geometry described by
## @var{geom} and by the level-set function @var{phi}.  Note that
## @var{geom} must contain absolute coordinates as set by
## @code{ls_absolute_geom}.
##
## If @var{glob} is set to @code{true}, then the mesh will contain
## the whole hold-all domain and not just the interior of the level-set
## geometry.  It will, however, be constructed such that the boundary
## of the level-set domain is resolved precisely, and such that the
## level-set domain can be described as a subset of the mesh triangles.
##
## The mesh points will be made up @emph{both} of grid nodes as well as
## intersection points.  While all intersection points will appear
## as mesh nodes, unless @var{glob} is set, only @strong{inner} grid nodes
## will correspond to mesh points.  The points on the mesh are numbered
## differently to the points in @var{geom}, although the indices can be
## translated both ways using @var{vmap} (see below).
##
## On the other hand, the boundary edges of the mesh match precisely the
## edges given in @code{@var{geom}.bedges}.  They have the same indices
## in both data structures.
##
## The returned struct @var{mesh} follows the format of the @code{msh}
## package, which means that it contains these fields:
##
## @table @code
## @item p
## 2 x @var{np} matrix which contains the x- and y-coordinates
## of the mesh points in its two rows.
##
## @item t
## 4 x @var{nt} matrix describing the mesh triangles.
## The first three rows contain the indices of mesh points making up the
## triangle, and the fourth row contains the index of the geometrical
## surface this triangle belongs to.  It is always set to @code{1} for now.
## The points are ordered @strong{counter-clockwise} (in the coordinate-system
## interpretation of the grid).
##
## @item e
## 7 x @var{ne} matrix describing the edges of the mesh.  They
## correspond precisely to @code{@var{geom}.bedges}.
## The rows of this matrix are:
##
## @table @asis
## @item 1--2
## Start- and end-point index.  They are ordered such that
## the level-set domain is @strong{on the left} of the edge.
##
## @item 3--4
## Always @code{0} for compatibility.
##
## @item 5
## The gamma component index (as per @code{@var{geom}.gamma}) that
## contains this edge.  Will be in the range 1--@code{@var{geom}.gamma.n}.
##
## @item 6--7
## Geometrical surface index of the domain parts to the right
## and left.  They are set to @code{0} and @code{1} for now.
## @end table
## @end table
##
## The second output, @var{vmap}, describes the mappings between geometrical
## node / intersection-point indices in @var{geom} and the indices of the
## mesh points used in @var{mesh}.  It is a struct with these fields:
##
## @table @code
## @item grid
## Maps the index of a node on the grid to the index of the
## corresponding mesh point.  The value is @code{NA} if the mesh
## does not contain the point.
##
## @item ispt
## Maps the index of an intersection point (according to
## @code{@var{geom}.ispts}) to the index of the corresponding mesh point.
##
## @item mesh
## Maps the index of a mesh point back to either the grid or intersection-point
## index.  A @strong{positive} value means that the mesh point is on the
## grid and has the respective index, while a @strong{negative} value means
## that it is an intersection point with index equal to the absolute value
## of the entry.
## @end table
##
## @seealso{ls_find_geometry, ls_absolute_geom, msh2m_structured_mesh}
## @end deftypefn

function [mesh, vmap] = ls_build_mesh (geom, phi, glob = false)
  if (nargin () < 2 || nargin () > 3)
    print_usage ();
  endif

  if (!isfield (geom.ispts, "coord") || !isfield (geom, "nodes"))
    error ("GEOM misses absolute coordinates, use ls_absolute_geom first");
  endif

  % Find inside array.
  L = geom.dim(1);
  M = geom.dim(2);
  inside = ls_inside (phi(:));

  % Call internal routine.
  [mesh, vmap] ...
    = __levelset_internal_mesh (L, M, inside, ...
                                geom.nodes.coord, geom.ispts.coord, ...
                                geom.elem.nodelist, geom.elem.index.inner, ...
                                geom.elem.index.outer, geom.elem.index.bdry, ...
                                geom.internal.bdryelSegments, glob);
  % FIXME: Should be 0 for outside triangles (in global mesh)?
  mesh.t(4, :) = 1; % Geometrical component.

  % Set mesh vertexMap entries to NA where they are -1.  This is not already
  % done in the C++ code since it uses integers instead of doubles
  % for the index arrays being built up.
  vmap.grid(vmap.grid < 0) = NA;
  assert (all (vmap.ispt > 0));
  assert (all (isfinite (vmap.mesh)));

  % Set boundary table from geom.bedges.  We only have to convert
  % geometrical ispt indices to mesh vertex indices.
  mesh.e = NA (7, geom.bedges.n);
  mesh.e(1 : 2, :) = vmap.ispt(geom.bedges.ispts');
  mesh.e([3:4, 6], :) = 0; % Reserved and outside on the right.
  mesh.e(5, :) = geom.bedges.comp; % Boundary component.
  mesh.e(7, :) = 1; % Domain on the left.
endfunction

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

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

% Test for missing absolute coordinates.
%!error <GEOM misses absolute coordinates, use ls_absolute_geom first>
%!  x = linspace (-10, 10, 10);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!  phi = ls_genbasic (XX, YY, "sphere", [0, 0], 5);
%!  phi = ls_normalise (phi, h);
%!  geom = ls_find_geometry (phi, h);
%!  ls_build_mesh (geom, phi);

% Helper function to verify that two points are in counter-clockwise
% direction with respect to a third one.
%!function verifyDirection (a, b, centre)
%!  v1 = [a - centre; 0];
%!  v2 = [b - centre; 0];
%!  x = cross (v1, v2);
%!  assert (x(1 : 2), zeros (2, 1));
%!  assert (x(3) > 0);
%!endfunction

% A function to verify certain expected properties / postconditions
% for a given mesh.
%!function verifyMesh (geom, phi, glob)
%!  [mesh, vmap] = ls_build_mesh (geom, phi, glob);
%!  nPts = prod (geom.dim);
%!  
%!  % Verify ordering of triangle points.
%!  for i = 1 : size (mesh.t, 2)
%!    verifyDirection (mesh.p(:, mesh.t(1, i)), mesh.p(:, mesh.t(2, i)), ...
%!                     mesh.p(:, mesh.t(3, i)));
%!  endfor
%!
%!  % Go through all grid points and verify them with the mesh.
%!  for i = 1 : nPts
%!    gridInd = vmap.grid(i);
%!    if (isna (gridInd))
%!      assert (!glob);
%!      continue;
%!    endif
%!
%!    assert (vmap.mesh(gridInd), i);
%!    assert (mesh.p(:, gridInd), geom.nodes.coord(i, :)');
%!  endfor
%!
%!  % Go through all intersection points and verify them.
%!  for i = 1 : geom.ispts.n
%!    gridInd = vmap.ispt(i);
%!    assert (vmap.mesh(gridInd), -i);
%!    assert (mesh.p(:, gridInd), geom.ispts.coord(i, :)');
%!  endfor
%!
%!  % Verify mesh.e against geom.bedges.
%!  for i = 1 : geom.bedges.n
%!    assert (mesh.e(1 : 2, i), vmap.ispt(geom.bedges.ispts(i, :)'));
%!    assert (mesh.e(5, i), geom.bedges.comp(i));
%!  endfor
%!endfunction
%
% Verify a given phi, by first constructing the geometry and mesh
% both in global and local mode as well as trying both phi and -phi.
%!function verifyPhi (phi, XX, YY, h)
%!  for s = [-1, 1]
%!    cur = phi * s;
%!    cur = ls_normalise (cur, h);
%!    geom = ls_find_geometry (cur, h);
%!    geom = ls_absolute_geom (geom, XX, YY);
%!
%!    verifyMesh (geom, cur, false);
%!    verifyMesh (geom, cur, true);
%!  endfor
%!endfunction

% Test against some example phis.  We do not use ls_get_tests, since
% we only want phis where we have a grid, too.
%!test
%!  n = 20;
%!  x = linspace (-10, 10, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi = ls_union (ls_genbasic (XX, YY, "sphere", [-6, -6], 3), ...
%!                  ls_genbasic (XX, YY, "sphere", [6, 6], 3));
%!  verifyPhi (phi, XX, YY, h);
%!
%!  phi = ls_setdiff (ls_genbasic (XX, YY, "sphere", [0, 0], 8), ...
%!                    ls_genbasic (XX, YY, "sphere", [0, 0], 4));
%!  verifyPhi (phi, XX, YY, h);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Demo.

% Build a mesh both in global and non-global mode and plot the result.
%!demo
%!  x = linspace (-10, 10, 11);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi = ls_union (ls_genbasic (XX, YY, "sphere", [5, 5], 4.5), ...
%!                  ls_genbasic (XX, YY, "sphere", [-2, -2], 4));
%!  phi = ls_normalise (phi, h);
%!  geom = ls_find_geometry (phi, h);
%!  geom = ls_absolute_geom (geom, XX, YY);
%!
%!  mesh = ls_build_mesh (geom, phi, false);
%!  meshGlob = ls_build_mesh (geom, phi, true);
%!
%!  figure ();
%!  hold ("on");
%!  trimesh (meshGlob.t(1 : 3, :)', meshGlob.p(1, :), meshGlob.p(2, :), "r");
%!  trimesh (mesh.t(1 : 3, :)', mesh.p(1, :), mesh.p(2, :), "g");
%!  for i = 1 : size (mesh.e, 2)
%!    plot (mesh.p(1, mesh.e(1 : 2, i)), mesh.p(2, mesh.e(1 : 2, i)), "b");
%!  endfor
%!  hold ("off");
%!  legend ("Global", "Domain", "Boundary");
%!  axis ("equal");
%!  axis ("square");
