/*****************************************************************************
*
* Copyright (c) 2000 - 2017, Lawrence Livermore National Security, LLC
* Produced at the Lawrence Livermore National Laboratory
* LLNL-CODE-442911
* All rights reserved.
*
* This file is  part of VisIt. For  details, see https://visit.llnl.gov/.  The
* full copyright notice is contained in the file COPYRIGHT located at the root
* of the VisIt distribution or at http://www.llnl.gov/visit/copyright.html.
*
* Redistribution  and  use  in  source  and  binary  forms,  with  or  without
* modification, are permitted provided that the following conditions are met:
*
*  - Redistributions of  source code must  retain the above  copyright notice,
*    this list of conditions and the disclaimer below.
*  - Redistributions in binary form must reproduce the above copyright notice,
*    this  list of  conditions  and  the  disclaimer (as noted below)  in  the
*    documentation and/or other materials provided with the distribution.
*  - Neither the name of  the LLNS/LLNL nor the names of  its contributors may
*    be used to endorse or promote products derived from this software without
*    specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR  IMPLIED WARRANTIES, INCLUDING,  BUT NOT  LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND  FITNESS FOR A PARTICULAR  PURPOSE
* ARE  DISCLAIMED. IN  NO EVENT  SHALL LAWRENCE  LIVERMORE NATIONAL  SECURITY,
* LLC, THE  U.S.  DEPARTMENT OF  ENERGY  OR  CONTRIBUTORS BE  LIABLE  FOR  ANY
* DIRECT,  INDIRECT,   INCIDENTAL,   SPECIAL,   EXEMPLARY,  OR   CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT  LIMITED TO, PROCUREMENT OF  SUBSTITUTE GOODS OR
* SERVICES; LOSS OF  USE, DATA, OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER
* CAUSED  AND  ON  ANY  THEORY  OF  LIABILITY,  WHETHER  IN  CONTRACT,  STRICT
* LIABILITY, OR TORT  (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY  WAY
* OUT OF THE  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*****************************************************************************/

// ************************************************************************* //
//  File: avtSurfCompPrepFilter.C
// ************************************************************************* //

#include <avtSurfCompPrepFilter.h>

#include <vtkMath.h>

#include <SimilarityTransformAttributes.h>

#include <avtCoordSystemConvert.h>
#include <avtImagePartition.h>
#include <avtSamplePointExtractor.h>
#include <avtSamplePointCommunicator.h>
#include <avtSamplePointToSurfaceFilter.h>
#include <avtSimilarityTransformFilter.h>
#include <avtSourceFromAVTDataset.h>

static float DegreesToRadians() {return 0.017453292f;};

// ****************************************************************************
//  Method: avtSurfCompPrepFilter constructor
//
//  Programmer: childs -- generated by xml2info
//  Creation:   Mon Jun 30 13:26:50 PST 2003
//
// ****************************************************************************

avtSurfCompPrepFilter::avtSurfCompPrepFilter()
{
}


// ****************************************************************************
//  Method: avtSurfCompPrepFilter destructor
//
//  Programmer: childs -- generated by xml2info
//  Creation:   Mon Jun 30 13:26:50 PST 2003
//
//  Modifications:
//
// ****************************************************************************

avtSurfCompPrepFilter::~avtSurfCompPrepFilter()
{
}


// ****************************************************************************
//  Method:  avtSurfCompPrepFilter::Create
//
//  Programmer: childs -- generated by xml2info
//  Creation:   Mon Jun 30 13:26:50 PST 2003
//
// ****************************************************************************

avtFilter *
avtSurfCompPrepFilter::Create()
{
    return new avtSurfCompPrepFilter();
}


// ****************************************************************************
//  Method:      avtSurfCompPrepFilter::SetAtts
//
//  Purpose:
//      Sets the state of the filter based on the attribute object.
//
//  Arguments:
//      a        The attributes to use.
//
//  Programmer: childs -- generated by xml2info
//  Creation:   Mon Jun 30 13:26:50 PST 2003
//
// ****************************************************************************

void
avtSurfCompPrepFilter::SetAtts(const AttributeGroup *a)
{
    atts = *(const SurfCompPrepAttributes*)a;
}


// ****************************************************************************
//  Method: avtSurfCompPrepFilter::Equivalent
//
//  Purpose:
//      Returns true if creating a new avtSurfCompPrepFilter with the given
//      parameters would result in an equivalent avtSurfCompPrepFilter.
//
//  Programmer: childs -- generated by xml2info
//  Creation:   Mon Jun 30 13:26:50 PST 2003
//
// ****************************************************************************

bool
avtSurfCompPrepFilter::Equivalent(const AttributeGroup *a)
{
    return (atts == *(SurfCompPrepAttributes*)a);
}


// ****************************************************************************
//  Method: avtSurfCompPrepFilter::Execute
//
//  Purpose:
//      Executes the surface comparison prep' filter.  This will transform
//      a dataset into the proper coordinate space, extract samples from it,
//      and then find an underlying surface.  Finally, it transforms it back
//      to the original coordinate space.
//  
//  Programmer: Hank Childs
//  Creation:   June 30, 2003
//
//  Modifications:
//
//    Hank Childs, Wed Jul  2 14:22:10 PDT 2003
//    Since components of coordinates gets swapped around by coord convert
//    filter, make sure that we are setting the right number of steps for
//    the right component.
//
//    Hank Childs, Tue Jul  8 22:20:30 PDT 2003
//    Fix parallel problem.
//
//    Jeremy Meredith, Mon Aug 10 11:17:31 EDT 2009
//    avtCoordSystemConvert has systems in its namespace now.
//
// ****************************************************************************

void
avtSurfCompPrepFilter::Execute(void)
{
    avtDataObject_p dObj = GetInput();
    avtDataset_p ds;
    CopyTo(ds, dObj);
    avtSourceFromAVTDataset termsrc(ds);
    avtDataObject_p data = termsrc.GetOutput();

    //
    // To do our sampling, we will first convert to the appropriate coordinate
    // system -- cartesian, cylindrical, or spherical.
    //
    avtCoordSystemConvert toCoordSys;
    toCoordSys.SetInputCoordSys(avtCoordSystemConvert::CARTESIAN);
    switch (atts.GetCoordSystem())
    {
      case SurfCompPrepAttributes::Cartesian:
        toCoordSys.SetOutputCoordSys(avtCoordSystemConvert::CARTESIAN);
        break;
      case SurfCompPrepAttributes::Cylindrical:
        toCoordSys.SetOutputCoordSys(avtCoordSystemConvert::CYLINDRICAL);
        break;
      case SurfCompPrepAttributes::Spherical:
        toCoordSys.SetOutputCoordSys(avtCoordSystemConvert::SPHERICAL);
        break;
    }
    toCoordSys.SetInput(data);

    //
    // The extractor only extracts sample points within the view frustum
    // {(-1., 1.), (-1., 1.), (0., 1)}.  So transform our data into that space.
    //
    avtSimilarityTransformFilter transToFrustum;
    SimilarityTransformAttributes st_atts;
    st_atts.SetDoScale(1);
    st_atts.SetDoTranslate(1);
    switch (atts.GetCoordSystem())
    {
      case SurfCompPrepAttributes::Cartesian:
      {
        double X1 = atts.GetXStart();
        double X2 = atts.GetXStop();
        double center = (X1 + X2) / 2;
        double range = X2 - X1;
        range = (range == 0. ? 1 : range); 
        double scale = 2. / range;
        st_atts.SetScaleX(scale);
        st_atts.SetTranslateX(-center*scale);
        double Y1 = atts.GetYStart();
        double Y2 = atts.GetYStop();
        center = (Y1 + Y2) / 2;
        range = Y2 - Y1;
        range = (range == 0. ? 1 : range); 
        scale = 2. / range;
        st_atts.SetTranslateY(-center*scale);
        st_atts.SetScaleY(scale);
        double Z1 = atts.GetZStart();
        double Z2 = atts.GetZStop();
        center = (Z1 + Z2) / 2;
        range = Z2 - Z1;
        range = (range == 0. ? 1 : range); 
        scale = 1. / range;
        st_atts.SetScaleZ(scale);
        st_atts.SetTranslateZ(0.5-center*scale);
        break;
      }  
      case SurfCompPrepAttributes::Cylindrical:
      {
        double X1 = atts.GetThetaStart() * DegreesToRadians();
        double X2 = atts.GetThetaStop() * DegreesToRadians();
        double center = (X1 + X2) / 2;
        double range = X2 - X1;
        range = (range == 0. ? 1 : range); 
        double scale = 2. / range;
        st_atts.SetScaleX(scale);
        st_atts.SetTranslateX(-center*scale);
        double Y1 = atts.GetZStart();
        double Y2 = atts.GetZStop();
        center = (Y1 + Y2) / 2;
        range = Y2 - Y1;
        range = (range == 0. ? 1 : range); 
        scale = 2. / range;
        st_atts.SetScaleY(scale);
        st_atts.SetTranslateY(-center*scale);
        double Z1 = atts.GetStartRadius();
        double Z2 = atts.GetEndRadius();
        center = (Z1 + Z2) / 2;
        range = Z2 - Z1;
        range = (range == 0. ? 1 : range); 
        scale = 1. / range;
        st_atts.SetScaleZ(scale);
        st_atts.SetTranslateZ(0.5-center*scale);
        break;
      }
      case SurfCompPrepAttributes::Spherical:
      {
        double X1 = atts.GetThetaStart() * DegreesToRadians();
        double X2 = atts.GetThetaStop() * DegreesToRadians();
        double center = (X1 + X2) / 2;
        double range = X2 - X1;
        range = (range == 0. ? 1 : range); 
        double scale = 2. / range;
        st_atts.SetScaleX(scale);
        st_atts.SetTranslateX(-center*scale);
        double Y1 = atts.GetPhiStart() * DegreesToRadians();
        double Y2 = atts.GetPhiStop() * DegreesToRadians();
        center = (Y1 + Y2) / 2;
        range = Y2 - Y1;
        range = (range == 0. ? 1 : range); 
        scale = 2. / range;
        st_atts.SetScaleY(scale);
        st_atts.SetTranslateY(-center*scale);
        double Z1 = atts.GetStartRadius();
        double Z2 = atts.GetEndRadius();
        center = (Z1 + Z2) / 2;
        range = Z2 - Z1;
        range = (range == 0. ? 1 : range); 
        scale = 1. / range;
        st_atts.SetScaleZ(scale);
        st_atts.SetTranslateZ(0.5-center*scale);
        break;
      }
    }
    transToFrustum.SetAtts(&st_atts);
    transToFrustum.SetInput(toCoordSys.GetOutput());

    //
    // The sample point extractor will do the actually extracting.  The number
    // of samples in each dimension depends on the attributes and what
    // coordinate system we are in.
    //
    int numX = 0;
    int numY = 0;
    int numZ = 0;
    switch (atts.GetCoordSystem())
    {
      case SurfCompPrepAttributes::Cartesian:
      {
         numX = atts.GetXSteps();
         numY = atts.GetYSteps();
         numZ = atts.GetZSteps();
         break;
      }  
      case SurfCompPrepAttributes::Cylindrical:
      {
         numX = atts.GetThetaSteps();
         numY = atts.GetZSteps();
         numZ = atts.GetRadiusSteps();
         break;
      }
      case SurfCompPrepAttributes::Spherical:
      {
         numX = atts.GetThetaSteps();
         numY = atts.GetPhiSteps();
         numZ = atts.GetRadiusSteps();
         break;
      }
    }
    avtSamplePointExtractor extractor(numX, numY, numZ);
    extractor.SetInput(transToFrustum.GetOutput());
    avtDataObject_p dob = extractor.GetOutput();

    avtImagePartition imagePartition(numX, numY);

#ifdef PARALLEL
    //
    // If we are in parallel, we will need to communicate the sample points
    // so that we can correctly infer the surface in the next step.
    //
    avtSamplePointCommunicator sampCommunicator;
    sampCommunicator.SetImagePartition(&imagePartition);
    sampCommunicator.SetInput(dob);
    dob = sampCommunicator.GetOutput();
#else
    //
    // The sample communicator will make this gets called, so we only need
    // to call this if we are not using that module.
    //
    int *dummy = new int[numY];
    for (int i = 0 ; i < numY ; i++)
        dummy[i] = 500;
    imagePartition.EstablishPartitionBoundaries(dummy);
    delete [] dummy;
#endif

    //
    // The sample point to surface filter will determine a surface from the
    // sample points.  The surface can be the front surface, the back
    // surface, and the middle surface.  The output surface will still be
    // in the image frustum.
    // 
    SurfaceType st = NOT_SPECIFIED;
    switch (atts.GetSurfaceType())
    {
      case SurfCompPrepAttributes::Closest:
        st = FRONT_SURFACE;
        break;
      case SurfCompPrepAttributes::Average:
        st = MIDDLE_SURFACE;
        break;
      case SurfCompPrepAttributes::Farthest:
        st = BACK_SURFACE;
        break;
    }
    avtSamplePointToSurfaceFilter sampsToSurface;
    sampsToSurface.SetImagePartition(&imagePartition);
    sampsToSurface.SetSurfaceType(st);
    sampsToSurface.SetInput(dob);

    //
    // Now transform the data out of the image frustum and back into the
    // coordinate system space.
    //
    avtSimilarityTransformFilter outOfFrustum;
    st_atts.SetDoScale(1);
    st_atts.SetDoTranslate(1);
    switch (atts.GetCoordSystem())
    {
      case SurfCompPrepAttributes::Cartesian:
      {
        double X1 = atts.GetXStart();
        double X2 = atts.GetXStop();
        double center = (X1 + X2) / 2;
        double range = X2 - X1;
        range = (range == 0. ? 1 : range); 
        double scale = range / 2.;
        st_atts.SetTranslateX(center);
        st_atts.SetScaleX(scale);
        double Y1 = atts.GetYStart();
        double Y2 = atts.GetYStop();
        center = (Y1 + Y2) / 2;
        range = Y2 - Y1;
        range = (range == 0. ? 1 : range); 
        scale = range / 2.;
        st_atts.SetScaleY(scale);
        st_atts.SetTranslateY(center);
        double Z1 = atts.GetZStart();
        double Z2 = atts.GetZStop();
        center = (Z1 + Z2) / 2;
        range = Z2 - Z1;
        range = (range == 0. ? 1 : range); 
        scale = range;
        st_atts.SetScaleZ(scale);
        st_atts.SetTranslateZ(center - 0.5*scale);
        break;
      }  
      case SurfCompPrepAttributes::Cylindrical:
      {
        double X1 = atts.GetThetaStart() * DegreesToRadians();
        double X2 = atts.GetThetaStop() * DegreesToRadians();
        double center = (X1 + X2) / 2;
        double range = X2 - X1;
        range = (range == 0. ? 1 : range); 
        double scale = range / 2.;
        st_atts.SetScaleX(scale);
        st_atts.SetTranslateX(center);
        double Y1 = atts.GetZStart();
        double Y2 = atts.GetZStop();
        center = (Y1 + Y2) / 2;
        range = Y2 - Y1;
        range = (range == 0. ? 1 : range); 
        scale = range / 2.;
        st_atts.SetScaleY(scale);
        st_atts.SetTranslateY(center);
        double Z1 = atts.GetStartRadius();
        double Z2 = atts.GetEndRadius();
        center = (Z1 + Z2) / 2;
        range = Z2 - Z1;
        range = (range == 0. ? 1 : range); 
        scale = range;
        st_atts.SetScaleZ(scale);
        st_atts.SetTranslateZ(center - 0.5*scale);
        break;
      }
      case SurfCompPrepAttributes::Spherical:
      {
        double X1 = atts.GetThetaStart() * DegreesToRadians();
        double X2 = atts.GetThetaStop() * DegreesToRadians();
        double center = (X1 + X2) / 2;
        st_atts.SetTranslateX(center);
        double range = X2 - X1;
        range = (range == 0. ? 1 : range); 
        double scale = range / 2.;
        st_atts.SetScaleX(scale);
        double Y1 = atts.GetPhiStart() * DegreesToRadians();
        double Y2 = atts.GetPhiStop() * DegreesToRadians();
        center = (Y1 + Y2) / 2;
        st_atts.SetTranslateY(center);
        range = Y2 - Y1;
        range = (range == 0. ? 1 : range); 
        scale = range / 2.;
        st_atts.SetScaleY(scale);
        double Z1 = atts.GetStartRadius();
        double Z2 = atts.GetEndRadius();
        center = (Z1 + Z2) / 2;
        range = Z2 - Z1;
        range = (range == 0. ? 1 : range); 
        scale = range;
        st_atts.SetScaleZ(scale);
        st_atts.SetTranslateZ(center - 0.5*scale);
        break;
      }
    }
    outOfFrustum.SetAtts(&st_atts);
    outOfFrustum.SetInput(sampsToSurface.GetOutput());

    //
    // We are now back in the coordinate system.  Let's get back to Cartesian
    // coordinates.
    //
    avtCoordSystemConvert backToCartesian;
    switch (atts.GetCoordSystem())
    {
      case SurfCompPrepAttributes::Cartesian:
        backToCartesian.SetInputCoordSys(avtCoordSystemConvert::CARTESIAN);
        break;
      case SurfCompPrepAttributes::Cylindrical:
        backToCartesian.SetInputCoordSys(avtCoordSystemConvert::CYLINDRICAL);
        break;
      case SurfCompPrepAttributes::Spherical:
        backToCartesian.SetInputCoordSys(avtCoordSystemConvert::SPHERICAL);
        break;
    }
    backToCartesian.SetOutputCoordSys(avtCoordSystemConvert::CARTESIAN);
    backToCartesian.SetInput(outOfFrustum.GetOutput());

    //
    // The last few hundred lines of code have set up a network.  Now force
    // that network to execute.
    //
    backToCartesian.Update(GetGeneralContract());

    //
    // Now copy the output of that execution to be the output of this filter.
    //
    GetOutput()->Copy(*(backToCartesian.GetOutput()));
}


// ****************************************************************************
//  Method: avtSurfCompPrepFilter::AdditionalPipelineFilters
//
//  Purpose:
//      States that there are many filters in the sub-pipeline.
//
//  Programmer: Hank Childs
//  Creation:   July 1, 2003
//
// ****************************************************************************

int
avtSurfCompPrepFilter::AdditionalPipelineFilters(void)
{
    int subfilts = 6;
#ifdef PARALLEL
    subfilts += 1;
#endif
    return subfilts;
}


// ****************************************************************************
//  Method: avtSurfCompPrepFilter::UpdateDataObjectInfo
//
//  Purpose:
//      Tells the output that we now have a surface, not a volume.
//
//  Programmer: Hank Childs
//  Creation:   July 1, 2003
//
//  Modifications:
//
//    Hank Childs, Thu Jul  3 11:21:58 PDT 2003
//    Don't take normals because it adds points and re-orders triangles.
//
// ****************************************************************************

void
avtSurfCompPrepFilter::UpdateDataObjectInfo(void)
{
    avtDataObject_p output = GetOutput();
    output->GetInfo().GetValidity().InvalidateZones();
    output->GetInfo().GetValidity().SetNormalsAreInappropriate(true);
    output->GetInfo().GetAttributes().SetTopologicalDimension(2);
    output->GetInfo().GetAttributes().SetContainsGhostZones(AVT_NO_GHOSTS);
}


