/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     level_common.c                                                 */
/*                                                                          */
/* description:  routines for extracting level set of a piecewise linear    */
/*               fe-function, 3d part                                       */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*            file includes ../Common/level_common.c                        */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Alfred Schmidt                                               */
/*             Zentrum fuer Technomathematik                                */
/*             Fachbereich 3 Mathematik/Informatik                          */
/*             Universitaet Bremen                                          */
/*             Bibliothekstr. 2                                             */
/*             D-28359 Bremen, Germany                                      */
/*                                                                          */
/*             Kunibert G. Siebert                                          */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*  http://www.alberta-fem.de/                                              */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*  (c) by A. Schmidt and K.G. Siebert (1996-2004)                          */
/*                                                                          */
/*     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 2 of the License, or    */
/*     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.                         */
/*--------------------------------------------------------------------------*/


REAL level_element_det_3d(const REAL_D coord[N_VERTICES_2D])
{
  REAL_D  e1, e2, normal;
  int     i;

  for (i = 0; i < DIM_OF_WORLD; i++)
  {
    e1[i] = coord[1][i] - coord[0][i];
    e2[i] = coord[2][i] - coord[0][i];
  }

  normal[0] = e1[1]*e2[2] - e1[2]*e2[1];
  normal[1] = e1[2]*e2[0] - e1[0]*e2[2];
  normal[2] = e1[0]*e2[1] - e1[1]*e2[0];
  
  return(sqrt(SCP_DOW(normal,normal)));
}

void level_coord_to_world_3d(const REAL_D coord[N_VERTICES_2D],
			     const REAL_B lambda,
			     REAL_D world)
{
  int      k;

  for (k = 0; k < DIM_OF_WORLD; k++)
    world[k] = (lambda[0]*coord[0][k] + lambda[1]*coord[1][k]
		   + lambda[2]*coord[2][k]);
  return;
}

void level_coord_to_el_coord_3d(const REAL_B v_lambda[N_VERTICES_2D],
				const REAL_B lambda,
				REAL_B el_lambda)
{
  int     k;

  for (k = 0; k < N_VERTICES_3D; k++)
    el_lambda[k] = (lambda[0]*v_lambda[0][k] + lambda[1]*v_lambda[1][k]
		    + lambda[2]*v_lambda[2][k]);

  return;
}

static int   n_tri = 0, n_quad = 0;

/****************************************************************************/
/*  N=3:  triangle       2                 1                                */
/*                      /\                /\                                */
/*                     /  \      or      /  \                               */
/*                   0/____\1          0/____\2                             */
/*                                                                          */
/*  N=4: quadrilateral                                                      */
/*                    3______2          3______2                            */
/*                    |      \          |2   1/\                            */
/*                    |       \         | II / 2\                           */
/*                    |        \        |   /    \                          */
/*                    |         \       |  /  I   \                         */
/*                    |          \      |0/0      1\                        */
/*                    |___________\     |/__________\                       */
/*                    0            1    0           1                       */
/*                                                                          */
/****************************************************************************/

static void new_face_3d(const EL_INFO *el_info, REAL v[], int N,
			int face, REAL_B lambda[])
{
  int      n, j, k;
  REAL_D   coord[N_VERTICES_2D];

  if (init_element &&
      !(*init_element)(el_info, v, N, face, (const REAL_B *)lambda))
    return;

  if (N == 3)
    n_tri++;
  else
    n_quad++;

  for (n = 0; n < 3; n++)
  {
    for (k = 0; k < DIM_OF_WORLD; k++)
    {
      coord[n][k] = 0.0;
      for (j = 0; j < N_VERTICES_3D; j++)
	coord[n][k] += lambda[n][j]*el_info->coord[j][k];
    }
  }

  if (cal_element)
    (*cal_element)(el_info, v, 0, face,
		   (const REAL_B *)lambda, (const REAL_D *)coord);

  if (N == 4)
  {
    for (k = 0; k < DIM_OF_WORLD; k++)
    {
      coord[1][k] = coord[2][k];
      coord[2][k] = 0.0;
      for (j = 0; j < N_VERTICES_3D; j++)
	coord[2][k] += lambda[3][j]*el_info->coord[j][k];
    }

    for (j = 0; j < N_VERTICES_3D; j++)
    {
      lambda[1][j] = lambda[2][j];
      lambda[2][j] = lambda[3][j];
    }

    if (cal_element)
      (*cal_element)(el_info, v, 1, face,
		     (const REAL_B *)lambda, (const REAL_D *)coord);
  }

  return;
}

static void level_fct_3d(const EL_INFO *el_info, void *dummy)
{
  FUNCNAME("level_fct");
  static REAL_B lambdaT[N_VERTICES_3D] =
    {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}};
  static REAL_B lambdaf[N_FACES_3D][N_VERTICES_2D] =
    {{{0,1,0,0},{0,0,1,0},{0,0,0,1}},
     {{1,0,0,0},{0,0,1,0},{0,0,0,1}},
     {{1,0,0,0},{0,1,0,0},{0,0,0,1}},
     {{1,0,0,0},{0,1,0,0},{0,0,1,0}}};
  REAL        s;
  REAL_B      v;
  REAL_BB     lambda;
  int         i, j, i1, i2;
  int         n_p = 0, n_m = 0, n_0 = 0, index_p[4], index_m[4], index_0[4];

  get_real_vec(v, el_info->el, level);

/****************************************************************************/
/*  count the number of positve, negative and zero values at the vertices   */
/****************************************************************************/

  for (i = 0; i < N_VERTICES_3D; i++) 
  {
    if (v[i] - level_value > small)
      index_p[n_p++] = i;
    else if (v[i] - level_value < -small)
      index_m[n_m++] = i;
    else
      index_0[n_0++] = i;
  }

  TEST_EXIT(n_p + n_m + n_0 == N_VERTICES_3D,
	    "n_p + n_m + n_0 = %d != N_VERTICES_3D\n", n_p + n_m + n_0);

  switch(n_0)
  {
  case 4:
/****************************************************************************/
/*  all four faces belong to the level:                                     */
/*    =>  four triangles (if not collected by the neighbour!)               */
/****************************************************************************/

    for (i = 0; i < N_NEIGH_3D; i++)
      new_face_3d(el_info, v, 3, i, lambdaf[i]);

    break;
  case 3:
/****************************************************************************/
/*  one faces belongs to the level:                                         */
/*    =>  one triangle (if not collected by the neighbour!)                 */
/****************************************************************************/
    
    i = n_p ? index_p[0] : index_m[0];
    new_face_3d(el_info, v, 3, i, lambdaf[i]);

    break;
  case 2:
/****************************************************************************/
/*  one of the other vertices is positive and the other negativ             */
/*    => one (interior) triangle                                            */
/*  otherwise the zero level is only an edge => nothing to do               */
/****************************************************************************/

    if (n_p == 1  &&  n_m  == 1)
    {
      i1 = index_p[0];
      i2 = index_m[0];

      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
      {
	lambda[0][j] = lambdaT[index_0[0]][j];
	lambda[1][j] = lambdaT[index_0[1]][j];
	lambda[2][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];
      }

      new_face_3d(el_info, v, 3, -1, lambda);
    }
    break;
  case 1:
/****************************************************************************/
/*  there are either two positive and one negative vertex or one positive   */
/*  and two negative vertices => one (interior) triangle                    */
/*  otherwise the zero level is a single vertex => nothing to do            */
/****************************************************************************/

    if (n_p == 2  &&  n_m == 1)
    {
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[0][j] = lambdaT[index_0[0]][j];

      i1 = index_m[0];

      i2 = index_p[0];
      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[1][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];

      i2 = index_p[1];
      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[2][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];

      new_face_3d(el_info, v, 3, -1, lambda);
    }
    else if (n_p == 1  &&  n_m == 2)
    {

      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[0][j] = lambdaT[index_0[0]][j];

      i1 = index_p[0];

      i2 = index_m[0];
      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[1][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];

      i2 = index_m[1];
      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[2][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];

      new_face_3d(el_info, v, 3, -1, lambda);
    }

    break;
  case 0:
/****************************************************************************/
/* all other cases:                                                         */
/*   4 positive vertices: nothing                                           */
/*   3 positive vertices and 1 negative vertex: 1 interior triangle         */
/*   2 positive vertices and 2 negative vertices: 1 interior quadrilateral  */
/*   1 positive vertex and 3 negative vertices: 1 interior triangle         */
/*   4 negative vertices: nothing                                           */
/****************************************************************************/

    switch (n_p)
    {
    case 3:
      i1 = index_m[0];

      for (i = 0; i < 3; i++)
      {
	i2 = index_p[i];
	s = (level_value - v[i1])/(v[i2] - v[i1]);
	for (j = 0; j < N_VERTICES_3D; j++)
	  lambda[i][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];
      }
      new_face_3d(el_info, v, 3, -1, lambda);

      break;
    case 2:
      i1 = index_p[0];
      i2 = index_m[0];
      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[0][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];

      i1 = index_m[0];
      i2 = index_p[1];
      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[1][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];

      i1 = index_p[1];
      i2 = index_m[1];
      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[2][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];

      i1 = index_m[1];
      i2 = index_p[0];
      s = (level_value - v[i1])/(v[i2] - v[i1]);
      for (j = 0; j < N_VERTICES_3D; j++)
	lambda[3][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];

      new_face_3d(el_info, v, 4, -1, lambda);

      break;
    case 1:
      i1 = index_p[0];

      for (i = 0; i < 3; i++)
      {
	i2 = index_m[i];
	s = (level_value - v[i1])/(v[i2] - v[i1]);
	for (j = 0; j < N_VERTICES_3D; j++)
	  lambda[i][j] = (1.0 - s)*lambdaT[i1][j] + s*lambdaT[i2][j];
      }
      new_face_3d(el_info, v, 3, -1, lambda);

      break;
    }
    break;
  }
  return;
}

#if 0 /* cH: just too lazy ATM. */
/****************************************************************************/
/*  functions for writing a movi-file of the iso-level                      */
/****************************************************************************/

static int  nt, nv;
static FILE *movi_file;
static int (*is_domain)(const EL *);

static int movi_init0(const EL_INFO *el_info, REAL v[], int N, int f,
		      const REAL lambda[][DIM+1])
{
  EL  *n;

  el_info->el->mark = 0;
  if (f >= 0)
  {
    if ((n = NEIGH(el_info->el,el_info)[f]))
    {
      if (!n->mark)  return(0);
      if (is_domain && !is_domain(el_info->el) && !is_domain(n)) return(0);
    }
    else
    {
      if (is_domain && !is_domain(el_info->el)) return(0);
    }
  }

  if (N == 3)
    nt++;
  else
    nt += 2;

  nv += N;

  return(0);
}

static int movi_init1(const EL_INFO *el_info, REAL v[], int N, int f,
		      const REAL lambda[][DIM+1])
{
  EL  *n;

  el_info->el->mark = 0;
  if (f >= 0)
  {
    if ((n = NEIGH(el_info->el,el_info)[f]))
    {
      if (!n->mark)  return(0);
      if (is_domain && !is_domain(el_info->el) && !is_domain(n)) return(0);
    }
    else
    {
      if (is_domain && !is_domain(el_info->el)) return(0);
    }
  }

  return(1);
}

static void movi_element(const EL_INFO *el_info, REAL v[], int i, int f,
			 const REAL vlambda[][DIM+1], const REAL_D coord[DIM])
{
  int   j;

  if (i == 0)
  {
    for (j = 0; j < 3; j++)
      fprintf(movi_file, "%12.5e %12.5e %12.5e\n", 
	      coord[j][0], coord[j][1], coord[j][2]);
    nv += 3;
  }
  else
  {
    fprintf(movi_file, "%12.5e %12.5e %12.5e\n", 
	    coord[2][0], coord[2][1], coord[2][2]);
    nv++;
  }

  return;
}

static int movi_init2(const EL_INFO *el_info, REAL v[], int N, int f,
		      const REAL lambda[][DIM+1])
{
  EL  *n;

  el_info->el->mark = 0;
  if (f >= 0)
  {
    if ((n = NEIGH(el_info->el,el_info)[f]))
    {
      if (!n->mark)  return(0);
      if (is_domain && !is_domain(el_info->el) && !is_domain(n)) return(0);
    }
    else
    {
      if (is_domain && !is_domain(el_info->el)) return(0);
    }
  }

  if (N == 3)
  {
    fprintf(movi_file, "%4d %4d %4d\n", nv+1, nv+2, -(nv+3));
    nv += 3;
  }
  else
  {
    fprintf(movi_file, "%4d %4d %4d\n", nv+1, nv+2, -(nv+3));
    fprintf(movi_file, "%4d %4d %4d\n", nv+1, nv+3, -(nv+4));
    nv += 4;
  }
  return(0);
}

void movi_find_level(MESH *mesh, const DOF_REAL_VEC *level, REAL value, 
		     int (*is_d)(const EL *),  const char *filename)
{
  FUNCNAME("movi_find_level");
  int n_v;

  if (!(movi_file = fopen(filename, "w")))
  {
    ERROR("can not open file <%s>\n", filename);
    return;
  }

  is_domain = is_d;

  nv = nt = 0;
  set_element_mark(mesh, CALL_LEAF_EL, 1);
  find_level(mesh, FILL_COORDS|FILL_NEIGH, level, value, movi_init0, NULL);
  fprintf(movi_file, "1 %d %d %d 0\n", n_v = nv, nt, 3*nt);
  fprintf(movi_file, "1 %d\n", nt);

  nv = nt = 0;
  set_element_mark(mesh, CALL_LEAF_EL, 1);
  find_level(mesh, FILL_COORDS|FILL_NEIGH, level, value, movi_init1, 
	     movi_element);

  nv = nt = 0;
  set_element_mark(mesh, CALL_LEAF_EL, 1);
  find_level(mesh, FILL_COORDS|FILL_NEIGH, level, value, movi_init2, NULL);
  set_element_mark(mesh, CALL_LEAF_EL, 0);
  if (nv != n_v)
  {
    ERROR("nv = %d != %d = n_v\n", nv, n_v);
    WAIT;
  }
  fclose(movi_file);
  return;
}
#endif /* 0 */
