cgma
Faceter Class Reference

#include <Faceter.hpp>

List of all members.

Public Member Functions

 Faceter (RefFace *face)
 ~Faceter ()
CubitStatus facet_surface (DLIList< CubitFacet * > &results, DLIList< CubitPoint * > &point_list)

Private Member Functions

CubitStatus facet_loop (DLIList< CubitPoint * > *loop_ptr, DLIList< CubitFacet * > &results)
CubitStatus facet_factory (FaceterPointData *curr_faceter_data, CubitFacet *&new_facet, DLIList< FaceterPointData * > &order_list)
CubitBoolean avoid_facet (FaceterPointData *curr_faceter_data, DLIList< FaceterPointData * > &order_list)
CubitStatus get_boundary_points (DLIList< DLIList< CubitPoint * > * > &boundary_point_loops) const
CubitStatus get_curve_facets (RefEdge *curve, DLIList< CubitPoint * > &segments) const
void max_min_edge_ratio (DLIList< DLIList< CubitPoint * > * > &boundary_point_loops, double &ratio, double &cell_size)
CubitStatus interior_angle (DLIList< CubitPoint * > *loop_ptr, double &my_angle)

Private Attributes

RefFacethisRefFacePtr
PointGridSearchgridSearchPtr
const double gridCellScale
CubitBoolean avoidedOverlap
DLIList< CubitPoint * > * globalPointList

Detailed Description

Definition at line 33 of file Faceter.hpp.


Constructor & Destructor Documentation

Definition at line 61 of file Faceter.cpp.


Member Function Documentation

CubitBoolean Faceter::avoid_facet ( FaceterPointData curr_faceter_data,
DLIList< FaceterPointData * > &  order_list 
) [private]

Definition at line 313 of file Faceter.cpp.

{
  
  int ii;
  CubitPoint *tmp_point;
  FaceterPointData *prev = curr_faceter_data->get_prev();
  FaceterPointData *next = curr_faceter_data->get_next();
    //get the closest edges to this point.
  CubitVector curr_v = curr_faceter_data->coordinates();
  CubitVector prev_v = prev->coordinates();
  CubitVector next_v = next->coordinates();
  double r1 = (curr_v - prev_v).length();
  double r2 = (curr_v - next_v).length();
  double radius;
  double angle;
  if ( r1 > r2 )
    radius = r1;
  else
    radius = r2;
  gridSearchPtr->set_neighborhood_bounds(curr_v, radius);
  DLIList<CubitPoint*> neighborhood_points;
  DLIList<CubitPoint*> close_points, loop_list;
  gridSearchPtr->get_neighborhood_points(neighborhood_points);
    //Loop through and see if there are nodes in here closer
    //than the r1 or r2.
  FaceterPointData *tmp_faceter;
  CubitVector tmp_v;
  radius *= radius;
  double angle1, angle2;
  for (ii = neighborhood_points.size(); ii > 0; ii-- )
  {
    tmp_point = neighborhood_points.get_and_step();
    tmp_faceter = (FaceterPointData*) tmp_point;
    if ( tmp_faceter == curr_faceter_data ||
         tmp_faceter == prev ||
         tmp_faceter == next ||
         tmp_faceter == next->get_next() ||
         tmp_faceter == prev->get_prev() )
      continue;
    tmp_v = tmp_faceter->coordinates();
    if ( (curr_v-tmp_v).length_squared() < radius )
      close_points.append(tmp_point);
  }
    //Now of these close points if any of them make
    //good trinagles for the other nodes then don't do
    //this triangle...
  CubitBoolean abort_facet = CUBIT_FALSE;
  for ( ii = close_points.size(); ii > 0; ii-- )
  {
    tmp_point = close_points.get_and_step();
    loop_list.clean_out();
    loop_list.append(tmp_point);
    loop_list.append((CubitPoint*)prev);
    loop_list.append((CubitPoint*)curr_faceter_data);
    loop_list.step();
    interior_angle(&loop_list, angle1);
    loop_list.clean_out();
    loop_list.append((CubitPoint*)curr_faceter_data);
    loop_list.append((CubitPoint*)next);
    loop_list.append(tmp_point);
    loop_list.step();
    interior_angle(&loop_list, angle2);
    if ( angle1 < DEGREES_TO_RADIANS(160) &&
         angle2 < DEGREES_TO_RADIANS(160))
    {
      int tmp_debug = 1;
      if ( tmp_debug )
      {
        GfxDebug::draw_point(prev->coordinates(), CUBIT_GREEN_INDEX);
        GfxDebug::draw_point(curr_faceter_data->coordinates(), CUBIT_RED_INDEX);
        GfxDebug::draw_point(tmp_point->coordinates(), CUBIT_BLUE_INDEX);
        GfxDebug::draw_point(next->coordinates(), CUBIT_MAGENTA_INDEX);
        loop_list.clean_out();
        loop_list.append(tmp_point);
        loop_list.append((CubitPoint*)prev);
        loop_list.append((CubitPoint*)curr_faceter_data);
        loop_list.step();
        interior_angle(&loop_list, angle1);
      }
      abort_facet = CUBIT_TRUE;
      break;
    }
  }
  if ( abort_facet )
  {
      //We need to add curr_facerter_data back to the list.
    order_list.insert(curr_faceter_data);
      //Set the angle above 360...
    angle = curr_faceter_data->get_interior_angle();
    curr_faceter_data->set_interior_angle(angle + 2*CUBIT_PI);
    avoidedOverlap = CUBIT_TRUE;
    return CUBIT_TRUE;
  }
  return CUBIT_FALSE;
}
CubitStatus Faceter::facet_factory ( FaceterPointData curr_faceter_data,
CubitFacet *&  new_facet,
DLIList< FaceterPointData * > &  order_list 
) [private]

Definition at line 250 of file Faceter.cpp.

{
  double angle;
  DLIList<CubitPoint*> loop_list;
  new_facet = NULL;
  if ( avoid_facet(curr_faceter_data, order_list) )
    return CUBIT_SUCCESS;
  FaceterPointData *prev = curr_faceter_data->get_prev();
  FaceterPointData *next = curr_faceter_data->get_next();
    //create the new facet.
    //create a triangle from these three points.
  new_facet = (CubitFacet*) new FaceterFacetData((CubitPoint*)prev,
                                                 (CubitPoint*)curr_faceter_data,
                                                 (CubitPoint*)next);
  CubitVector facet_normal = new_facet->normal();
  CubitVector surface_normal = thisRefFacePtr->normal_at(curr_faceter_data->coordinates());
  double dot = facet_normal%surface_normal;
  if ( dot < 0 )
  {
      //new need to try and do this in a different way...
    delete new_facet;
    new_facet = NULL;
    if ( order_list.size() < 3 )
    {
      PRINT_DEBUG_129("Last triangle is inverted. (surface %d)\n",
                      thisRefFacePtr->id());
      return CUBIT_FAILURE;
    }
      //Try and create facets on either side of this facet.
    return CUBIT_FAILURE;
  }
  
  int debug = 1;
  if ( debug )
    GfxDebug::draw_facet(new_facet, CUBIT_RED_INDEX);
    //Now fix up the ordered_list.
    //First update the angles.
  prev->set_next(next);
  next->set_prev(prev);
  loop_list.clean_out();
  loop_list.append(prev->get_prev());
  loop_list.append(prev);
  loop_list.append(next);
  loop_list.step();
  interior_angle(&loop_list, angle);
  prev->set_interior_angle(angle);
  loop_list.clean_out();
  loop_list.append(prev);
  loop_list.append(next);
  loop_list.append(next->get_next());
  loop_list.step();
  interior_angle(&loop_list, angle);
  next->set_interior_angle(angle);
    //Now do basically a sort again.  There is probably
    //a faster way to do this but there are also much slower
    //ways in worst case too. At least this has guaranteed O(nlogn).
  order_list.sort(FaceterPointData::sort_by_angle);
  CubitPoint *tmp_point = (CubitPoint*) curr_faceter_data;
  gridSearchPtr->remove_point(tmp_point);
  return CUBIT_SUCCESS;
}
CubitStatus Faceter::facet_loop ( DLIList< CubitPoint * > *  loop_ptr,
DLIList< CubitFacet * > &  results 
) [private]

Definition at line 153 of file Faceter.cpp.

{
    //Assume there is just this loop and facet it as if there was nothing
    //in the middle.  This is about the most stupid, simplistic method
    //of faceting I could think of.  I'm sure anyone reading this could
    //think of something more sophisticated and better.

    //First get the inteior angle for all of the points.
  DLIList <FaceterPointData*> ordered_point_list;
  int ii;
  double angle;
  CubitStatus stat;
  CubitPoint *curr_point;
  FaceterPointData *curr_faceter_data, *prev, *next;
  loop_ptr->reset();
  int debug = 0;
  for ( ii = 0; ii < loop_ptr->size(); ii++ )
  {
    stat = interior_angle(loop_ptr, angle);
    if ( stat == CUBIT_FAILURE )
      return CUBIT_FAILURE;
    curr_point = loop_ptr->get_and_step();
    if ( debug )
    {
      PRINT_INFO("%d\n",curr_point->id());
      GfxDebug::draw_point( curr_point->coordinates(), CUBIT_ORANGE_INDEX);
    }
    curr_faceter_data = (FaceterPointData*) curr_point;
    prev = (FaceterPointData*) loop_ptr->prev(2);
    next = (FaceterPointData*) loop_ptr->get();

    if ( angle < CUBIT_RESABS || angle > DEGREES_TO_RADIANS( 359.95 ))
    {
      
      PRINT_ERROR("Zero degree angle in loop\n");
      RefEdge *owner_e = (RefEdge*) curr_faceter_data->owner();
      RefEdge *prev_e = (RefEdge*) prev->owner();
      RefEdge *next_e = (RefEdge*) next->owner();
      PRINT_ERROR("On edge %d, with next %d and prev %d\n", owner_e->id(),
                  next_e->id(), prev_e->id());
      return CUBIT_FAILURE;
    }
    curr_faceter_data->set_interior_angle(angle);
    curr_faceter_data->set_prev(prev);
    curr_faceter_data->set_next(next);
    ordered_point_list.append(curr_faceter_data);
  }
  ordered_point_list.sort(FaceterPointData::sort_by_angle);

    //Now go through and just cut off the smallest triangles...
    //The list should be ordered largest angle to smallest.
    //so go from the end to the front.
  CubitFacet *new_facet = NULL;
  int safe_size = 4* ordered_point_list.size();
  int safe_count = 0;
  int redo_count = ordered_point_list.size();
  while (ordered_point_list.size() >= 3 &&
         safe_count < safe_size )
  {
    curr_faceter_data = ordered_point_list.pop();
    new_facet = NULL;
    stat = facet_factory(curr_faceter_data, new_facet, ordered_point_list);
    if ( new_facet != NULL )
      results.append(new_facet);
    if ( stat != CUBIT_SUCCESS )
      return stat;
    if ( safe_count == redo_count )
    {
      if ( avoidedOverlap )
      {
        CubitBoolean reorder = CUBIT_FALSE;
        for ( ii = ordered_point_list.size(); ii > 0; ii-- )
        {
          curr_faceter_data = ordered_point_list.get_and_step();
          angle = curr_faceter_data->get_interior_angle();
          if ( angle > 2*CUBIT_PI )
          {
            angle = angle - 2*CUBIT_PI;
            curr_faceter_data->set_interior_angle(angle);
            reorder = CUBIT_TRUE;
          }
        }
        if ( reorder )
          ordered_point_list.sort(FaceterPointData::sort_by_angle);
          //set the next redo to be later.
        avoidedOverlap = CUBIT_FALSE;
      }
      redo_count += redo_count;
    }
    safe_count++;
  }
  if ( ordered_point_list.size() < 3 )
    return CUBIT_SUCCESS;
  else
    return CUBIT_FAILURE;
}
CubitStatus Faceter::facet_surface ( DLIList< CubitFacet * > &  results,
DLIList< CubitPoint * > &  point_list 
)

Definition at line 77 of file Faceter.cpp.

{
  if ( DEBUG_FLAG(129) )
  {
    GfxDebug::clear();
    GfxDebug::draw_ref_face_edges(thisRefFacePtr);
    GfxDebug::flush();
    int debug = 0;
    if ( debug )
    {
      GfxDebug::mouse_xforms();
      GfxDebug::flush();
    }
  }
  if ( thisRefFacePtr->number_of_Loops() > 1 )
    return CUBIT_FAILURE;
    //Get the ordered boundary loops.
  int ii, jj;
  DLIList <DLIList<CubitPoint*>*>  boundary_point_loops;
  DLIList <CubitPoint*> *tmp_list_ptr;
  CubitStatus stat = get_boundary_points( boundary_point_loops );
  if ( stat != CUBIT_SUCCESS )
  {
      //clean up the data...
    for ( ii = boundary_point_loops.size(); ii > 0; ii-- )
    {
      tmp_list_ptr = boundary_point_loops.pop();
      for ( jj = tmp_list_ptr->size(); jj > 0; jj-- )
        delete tmp_list_ptr->pop();
      delete tmp_list_ptr;
    }
    return stat;
  }
    //Set up the gridsearch.
  double ratio = gridCellScale, cell_size = 0.0;
  max_min_edge_ratio(boundary_point_loops, ratio, cell_size);
  if (ratio <= gridCellScale) {
    ratio = gridCellScale;
  }
    //Get all of the points into a single list.
  for ( ii = boundary_point_loops.size(); ii > 0; ii-- )
  {
    tmp_list_ptr = boundary_point_loops.get_and_step();
    for ( jj = tmp_list_ptr->size(); jj > 0; jj-- )
    {
      globalPointList->append(tmp_list_ptr->get_and_step());
    }
  }
  gridSearchPtr = new PointGridSearch(*globalPointList,
                                      cell_size,
                                      ratio);
      //fill in the grid...
  for ( ii = globalPointList->size(); ii > 0; ii-- )
    gridSearchPtr->add_point(globalPointList->get_and_step());

    //Now start faceting.
  stat = facet_loop( boundary_point_loops.get(), results );
    //clean up the data...
  for ( ii = boundary_point_loops.size(); ii > 0; ii-- )
    delete boundary_point_loops.pop();
  if ( stat != CUBIT_SUCCESS )
  {
      //clean the data and return..
    for ( ii = results.size(); ii > 0; ii-- )
      delete results.pop();
    for ( ii = globalPointList->size(); ii > 0; ii-- )
      delete globalPointList->pop();
    return stat;
  }
    //Didn't add any points...
  point_list += *globalPointList;
  return CUBIT_SUCCESS;
}
CubitStatus Faceter::get_boundary_points ( DLIList< DLIList< CubitPoint * > * > &  boundary_point_loops) const [private]

Definition at line 513 of file Faceter.cpp.

{
  DLIList<DLIList<CoEdge*> > co_edge_loops;
  thisRefFacePtr->co_edge_loops(co_edge_loops);
  int ii, jj;
  DLIList <CubitPoint*> *new_point_loop_ptr, tmp_point_list;
  RefEdge *ref_edge_ptr;
  CoEdge *co_edge_ptr;
  CubitStatus stat;
  CubitSense sense;
  
  for ( ii = co_edge_loops.size(); ii > 0; ii-- )
  {
    DLIList <CoEdge*> &co_edge_list_ptr = co_edge_loops.get_and_step();
    new_point_loop_ptr = new DLIList <CubitPoint*>;
    for ( jj = co_edge_list_ptr.size(); jj > 0; jj-- )
    {
      co_edge_ptr = co_edge_list_ptr.get_and_step();
      ref_edge_ptr = co_edge_ptr->get_ref_edge_ptr();
      tmp_point_list.clean_out();
      stat = get_curve_facets( ref_edge_ptr, tmp_point_list );
      PRINT_DEBUG_129("curve %d has %d points\n",
                      ref_edge_ptr->id(),
                      tmp_point_list.size());
      if ( !stat )
        return CUBIT_FAILURE;
      tmp_point_list.reset();
        //the points are in order from start vertex to end vertex.
        //append them now according to the loop.
      sense = co_edge_ptr->get_sense();
      if ( CUBIT_FORWARD != sense )
        tmp_point_list.reverse();
        //Now take off the last point as it is a duplicate with the
        //other list...
      tmp_point_list.reset();
      delete tmp_point_list.pop();
      (*new_point_loop_ptr) += tmp_point_list;
    }
    CubitVector curr, prev;
    for ( jj = new_point_loop_ptr->size(); jj > 0; jj-- )
    {
      prev = new_point_loop_ptr->prev()->coordinates();
      curr = new_point_loop_ptr->get_and_step()->coordinates();
      if ( prev.about_equal(curr) )
      {
        PRINT_DEBUG_129("Points within tolerance in boundaryloop.\n");
        new_point_loop_ptr->back();
        delete new_point_loop_ptr->remove();
      }
    }
    boundary_point_loops.append(new_point_loop_ptr);
  }
  
  return CUBIT_SUCCESS;
}
CubitStatus Faceter::get_curve_facets ( RefEdge curve,
DLIList< CubitPoint * > &  segments 
) const [private]

Definition at line 569 of file Faceter.cpp.

{
//const double COS_ANGLE_TOL =  0.965925826289068312213715; // cos(15)
  const double COS_ANGLE_TOL =  0.984807753012208020315654; // cos(10)
//const double COS_ANGLE_TOL =  0.996194698091745545198705; // cos(5)
  GMem curve_graphics;
  const double dist_tol = GEOMETRY_RESABS;
  const double dist_tol_sqr = dist_tol*dist_tol;
  Curve* curve_ptr = curve->get_curve_ptr();
  curve_ptr->get_geometry_query_engine()->get_graphics( curve_ptr, &curve_graphics );
  
  GPoint* gp = curve_graphics.point_list();
  CubitPoint* last = (CubitPoint*) new FaceterPointData( gp->x, gp->y, gp->z );
  ((FaceterPointData*)last)->owner(dynamic_cast<RefEntity*>(curve));
  CubitVector lastv = last->coordinates();
  segments.append( last );
  GPoint* end = gp + curve_graphics.pointListCount - 1;
  
  for( gp++; gp < end; gp++ )
  {
    CubitVector pos(  gp->x, gp->y, gp->z );
    CubitVector step1 = (pos - lastv);
    double len1 = step1.length();
    if( len1 < dist_tol ) continue;
    
    GPoint* np = gp + 1;
    CubitVector next( np->x, np->y, np->z );
    CubitVector step2 = next - pos;
    double len2 = step2.length();
    if( len2 < dist_tol ) continue;
    
    double cosine = (step1 % step2) / (len1 * len2);
    if( cosine > COS_ANGLE_TOL ) continue;
    
    last = new FaceterPointData( pos );
    ((FaceterPointData*)last)->owner(dynamic_cast<RefEntity*>(curve));
    segments.append( last );
    lastv = last->coordinates();
  }
  
  CubitVector last_pos( gp->x, gp->y, gp->z );
  segments.last();
  while( (last_pos - (segments.get()->coordinates())).length_squared() < dist_tol_sqr )
  {
    delete segments.pop();
    segments.last();
  }
  CubitPoint *tmp_point = (CubitPoint*) new FaceterPointData( last_pos );
  segments.append( tmp_point );
  ((FaceterPointData*)tmp_point)->owner( dynamic_cast<RefEntity*>(curve) );
    
  // Now check if the segment list is reversed wrt the curve direction.
  segments.reset();
  double u1, u2;
  if( segments.size() > 2 )
  {
    u1 = curve->u_from_position( (segments.next(1)->coordinates()) );
    u2 = curve->u_from_position( (segments.next(2)->coordinates()) );    
  }
  else
  {
    u1 = curve->u_from_position( (segments.get()->coordinates() ) );
    u2 = curve->u_from_position( (segments.next()->coordinates()) );
  }
  if( (u2 < u1) && (curve->start_param() <= curve->end_param()) )
    segments.reverse();

    //Make sure we don't have duplicate points.
  int jj;
  CubitVector curr, prev;
  for ( jj = segments.size(); jj > 0; jj-- )
  {
    prev = segments.prev()->coordinates();
    curr = segments.get_and_step()->coordinates();
    if ( prev.about_equal(curr) )
    {
      PRINT_DEBUG_129("Points on curve %d within tolerance...\n", curve->id());
      segments.back();
      delete segments.remove();
    }
  }
  return CUBIT_SUCCESS;
}
CubitStatus Faceter::interior_angle ( DLIList< CubitPoint * > *  loop_ptr,
double &  my_angle 
) [private]

Definition at line 410 of file Faceter.cpp.

{
  CubitVector point = loop_ptr->get()->coordinates();
  CubitVector to_prev = loop_ptr->prev()->coordinates();
  to_prev  -= point;
  CubitVector to_next = loop_ptr->next()->coordinates();
  to_next -= point;
  CubitVector surf_norm = thisRefFacePtr->normal_at ( point );
  if ( surf_norm.length_squared() < CUBIT_RESABS )
  {
      //Try getting it at one of the other nodes...
    surf_norm = thisRefFacePtr->normal_at(loop_ptr->next()->coordinates() );
    if (surf_norm.length_squared() < CUBIT_RESABS )
    {
        //Try getting it at one of the other nodes...
      surf_norm = thisRefFacePtr->normal_at(loop_ptr->prev()->coordinates() );
      if (surf_norm.length_squared() < CUBIT_RESABS )
      {
        PRINT_ERROR("Trying to get normal at point %d on surf %d.\n"
                    "       Normal length being returned equals zero.\n",
                    loop_ptr->get()->id(), thisRefFacePtr->id() );
        return CUBIT_FAILURE;
      }
    }
  }
  double angle = surf_norm.vector_angle ( to_next, to_prev );

    //Now if the angle is very small (zero) we don't know if this
    //should be 0 or 2PI, so do some tests.
  const double NEAR_ZERO = DEGREES_TO_RADIANS( 0.15 );
  const double NEAR_360  = DEGREES_TO_RADIANS( 359.95 );
  if( NEAR_ZERO < angle && angle < NEAR_360 )
  {
    my_angle = angle;
    return CUBIT_SUCCESS;
  }
    //If we are close to zero or 2PI then take some other loop nodes
    //into consideration to see if we can get a better approximation
    //as to we are zero or 2 PI.
  CubitPoint *prev_node = loop_ptr->prev();
  CubitPoint *next_node = loop_ptr->next();
  int stop_it = loop_ptr->size();
  int count = 0;
  while (( angle < NEAR_ZERO || angle > NEAR_360 ) && ( count < stop_it ) )
  {
    prev_node = loop_ptr->prev( count + 2 );
    next_node = loop_ptr->next( count + 2 );
    to_prev = prev_node->coordinates() - point;
    to_next = next_node->coordinates() - point;
    angle = surf_norm.vector_angle ( to_next, to_prev );
    count++;
  }
  const double TWO_PI = 2.0 * CUBIT_PI;
  if ( angle < NEAR_ZERO )
    my_angle = TWO_PI;
  else if( angle < CUBIT_PI )
    my_angle = 0.0;
  else
    my_angle = TWO_PI;
  return CUBIT_SUCCESS;
}
void Faceter::max_min_edge_ratio ( DLIList< DLIList< CubitPoint * > * > &  boundary_point_loops,
double &  ratio,
double &  cell_size 
) [private]

Definition at line 472 of file Faceter.cpp.

{
  double max_edge_length = CUBIT_DBL_MIN;
  double min_edge_length = CUBIT_DBL_MAX;
  double sum = 0.0;
  int total_count = 0;
  DLIList<CubitPoint*> *loopPtr;
  CubitPoint *node1, *node2;
  CubitVector edge;
  for (int i = boundary_point_loops.size(); i > 0; i--) {
    loopPtr = boundary_point_loops.get_and_step();
    node2 = loopPtr->prev();
    for (int j = loopPtr->size(); j > 0; j--) {
      node1 = node2;
      node2 = loopPtr->get_and_step();
      CubitVector p1 = node1->coordinates();
      CubitVector p2 = node2->coordinates();
      edge = p2-p1;
      double edge_length = edge.length_squared();
      if (edge_length > max_edge_length) max_edge_length = edge_length;
      if (edge_length < min_edge_length) min_edge_length = edge_length;
      total_count++;
      sum += edge_length;
    }
  }

  if (min_edge_length > CUBIT_RESABS) {
    ratio = sqrt(max_edge_length/min_edge_length);
  }
  else {
    ratio = sqrt(max_edge_length);
  }
  if ( total_count > 0 && sum > 0 )
    cell_size = sqrt(sum/total_count);
  else
    cell_size = 0.0;
}

Member Data Documentation

Definition at line 53 of file Faceter.hpp.

Definition at line 54 of file Faceter.hpp.

const double Faceter::gridCellScale [private]

Definition at line 52 of file Faceter.hpp.

Definition at line 51 of file Faceter.hpp.

Definition at line 50 of file Faceter.hpp.


The documentation for this class was generated from the following files:
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines