/****************************************************************************
* MeshLab                                                           o o     *
* A versatile mesh processing toolbox                             o     o   *
*                                                                _   O  _   *
* Copyright(C) 2005                                                \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* All rights reserved.                                                      *
*                                                                           *
* 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         *
* (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 (http://www.gnu.org/licenses/gpl.txt)          *
* for more details.                                                         *
*                                                                           *
****************************************************************************/


#include "filter_aging.h"
using namespace vcg;


/* Constructor */
GeometryAgingPlugin::GeometryAgingPlugin()
{
    typeList << FP_ERODE;

    foreach(FilterIDType tt, types())
        actionList << new QAction(filterName(tt), this);
}


/* Destructor */
GeometryAgingPlugin::~GeometryAgingPlugin()
{
}

 MeshFilterInterface::FilterClass GeometryAgingPlugin::getClass(QAction *a)
{
  switch(ID(a))
  {
    case FP_ERODE :           return MeshFilterInterface::Remeshing;
        default :  assert(0);			return MeshFilterInterface::Generic;
  }
}




/* Returns the very short string describing each filtering action */
 QString GeometryAgingPlugin::filterName(FilterIDType filterId) const
{
    switch(filterId) {
        case FP_ERODE:
            return QString("Mesh aging and chipping simulation");
        default:
            assert(0);
    }
	return NULL;
}


/* Returns the longer string describing each filtering action */
 QString GeometryAgingPlugin::filterInfo(FilterIDType filterId) const
{
    switch(filterId) {
        case FP_ERODE:
            return QString("Simulates the aging effects due to small collisions or various chipping events");
        default:
            assert(0);
    }
	return NULL;
}

/* Initializes the list of parameters (called by the auto dialog framework) */
void GeometryAgingPlugin::initParameterSet(QAction *action, MeshModel &m, RichParameterSet &params)
{
    if( ID(action) != FP_ERODE)
    {
        assert (0);
        return;
    }
    bool hasQ = m.hasDataMask(MeshModel::MM_VERTQUALITY);
    std::pair<float,float> qRange(0.0, 0.0);;	// mesh quality range
    // retrieve mesh quality range
    if(hasQ)
    {
        qRange = tri::Stat<CMeshO>::ComputePerVertexQualityMinMax(m.cm);
        if(qRange.second <= qRange.first) hasQ=false;
    }

        params.addParam(new RichBool("ComputeCurvature", !hasQ, "ReCompute quality from curvature",
                "Compute per vertex quality values using mesh mean curvature <br>"
                "algorithm. In this way only the areas with higher curvature <br>"
                "will be eroded. If not checked, the quality values already <br>"
                "present over the mesh will be used."));
        params.addParam(new RichBool("SmoothQuality", false, "Smooth vertex quality",
                "Smooth per vertex quality values. This allows to extend the <br>"
                "area affected by the erosion process."));
        params.addParam(new RichAbsPerc("QualityThreshold", qRange.first+(qRange.second-qRange.first)*0.66,
                qRange.first, qRange.second, "Min quality threshold",
                "Represents the minimum quality value two vertices must have <br>"
                "to consider the edge they are sharing."));
        params.addParam(new RichAbsPerc("EdgeLenThreshold", m.cm.bbox.Diag()*0.02, 0,m.cm.bbox.Diag()*0.5,
                "Edge len threshold",
                "The minimum length of an edge. Useful to avoid the creation of too many small faces."));
        params.addParam(new RichAbsPerc("ChipDepth", m.cm.bbox.Diag()*0.05, 0, m.cm.bbox.Diag(),
                "Max chip depth", "The maximum depth of a chip."));
        params.addParam(new RichInt("Octaves", 3, "Fractal Octaves",
                "The number of octaves that are used in the generation of the <br>"
                "fractal noise using Perlin noise; reasonalble values are in the <br>"
                "1..8 range. Setting it to 1 means using a simple Perlin Noise."));
        params.addParam(new RichAbsPerc("NoiseFreqScale", m.cm.bbox.Diag()/10, 0, m.cm.bbox.Diag(), "Noise frequency scale",
                "Changes the noise frequency scale: this affects chip dimensions and <br>"
                "the distance between chips. The value denotes the average values <br>"
                "between two dents. Smaller number means small and frequent chips."));
        params.addParam(new RichFloat("NoiseClamp", 0.5, "Noise clamp threshold [0..1]",
                "All the noise values smaller than this parameter will be <br> "
                "considered as 0."));
        params.addParam(new RichFloat("DisplacementSteps", 10, "Displacement steps",
                "The whole displacement process is performed as a sequence of small <br>"
                "offsets applied on each vertex. This parameter represents the number <br>"
                "of steps into which the displacement process will be split. <br>"
                "Useful to avoid the introduction of self intersections. <br>"
                "Bigger number means better accuracy."));
        params.addParam(new RichBool("Selected", m.cm.sfn>0, "Affect only selected faces",
                "The aging procedure will be applied to the selected faces only."));
        params.addParam(new RichBool("StoreDisplacement", false, "Store erosion information",
                "Select this option if you want to store the erosion information <br>"
                "over the mesh. A new attribute will be added to each vertex <br>"
                "to contain the displacement offset applied to that vertex."));
}


/* The Real Core Function doing the actual mesh processing */
bool GeometryAgingPlugin::applyFilter(QAction *filter, MeshDocument &md, RichParameterSet &params, vcg::CallBackPos *cb)
{
    MeshModel &m=*(md.mm());
    if( ID(filter) != FP_ERODE)
    {
        assert (0);
        return false;
    }

    m.updateDataMask(MeshModel::MM_VERTQUALITY);

    bool curvature = params.getBool("ComputeCurvature");
    if(curvature)	computeMeanCurvature(m.cm);

    // other plugin parameters
    bool smoothQ = params.getBool("SmoothQuality");
    float qualityTh = params.getAbsPerc("QualityThreshold");
    float edgeLenTh = params.getAbsPerc("EdgeLenThreshold");
    float chipDepth = params.getAbsPerc("ChipDepth");
    int octaves = params.getInt("Octaves");
    float noiseScale = params.getAbsPerc("NoiseFreqScale");
    float noiseClamp = params.getFloat("NoiseClamp");
    int dispSteps = (int)params.getFloat("DisplacementSteps");
    bool selected = params.getBool("Selected");
    bool storeDispl = params.getBool("StoreDisplacement");

    // error checking on parameters values
    if(edgeLenTh == 0.0) edgeLenTh = m.cm.bbox.Diag()*0.02;
    if(chipDepth == 0.0) chipDepth = m.cm.bbox.Diag()*0.05;
    noiseClamp = math::Clamp<float>(noiseClamp, 0.0, 1.0);

            // quality threshold percentage value
            std::pair<float, float> qRange = tri::Stat<CMeshO>::ComputePerVertexQualityMinMax(m.cm);
            float qperc = (qualityTh-qRange.first) / (qRange.second-qRange.first);

            // compute mesh quality, if requested
            if(curvature) {
                if(cb) (*cb)(0, "Computing quality values...");
                computeMeanCurvature(m.cm);
            }

            // eventually, smooth quality values
            if(smoothQ) tri::Smooth<CMeshO>::VertexQualityLaplacian(m.cm);

            // if quality values have been recomputed quality threshold may not
            // be valid, so we recompute its absolute value using the percentage
            // value chosen by the user
            if(curvature || smoothQ) {
                qRange = tri::Stat<CMeshO>::ComputePerVertexQualityMinMax(m.cm);
                qualityTh = qRange.first + (qRange.second-qRange.first) * qperc;
            }

            // edge predicate
            QualityEdgePred ep = QualityEdgePred(selected, edgeLenTh, qualityTh);

            // refine needed edges
            refineMesh(m.cm, ep, selected, cb);

            // if requested, add erosion attribute to vertices and initialize it
            if(storeDispl) {
                CMeshO::PerVertexAttributeHandle<Point3m> vah = tri::Allocator<CMeshO>::GetPerVertexAttribute<Point3m>(m.cm, "Erosion");
                for(CMeshO::VertexIterator vi=m.cm.vert.begin(); vi!=m.cm.vert.end(); vi++)
                    vah[vi] = Point3m(0.0, 0.0, 0.0);
            }
            CMeshO::PerVertexAttributeHandle<Point3m> vah = vcg::tri::Allocator<CMeshO>::GetPerVertexAttribute<Point3m>(m.cm, "Erosion");

            // vertices along selection border will not be displaced
            if(selected) tri::UpdateSelection<CMeshO>::VertexFromFaceStrict(m.cm);

            // clear vertices V bit (will be used to mark the vertices as displaced)
            tri::UpdateFlags<CMeshO>::VertexClearV(m.cm);

            // displace vertices
            for(int i=0; i<dispSteps; i++) {
                GridStaticPtr<CFaceO, CMeshO::ScalarType> gM;
                gM.Set(m.cm.face.begin(), m.cm.face.end());

                if(cb) (*cb)( (i+1)*100/dispSteps, "Aging...");

                // blend together face normals and recompute vertex normal from these normals
                // to get smoother offset directions
                tri::Smooth<CMeshO>::FaceNormalLaplacianFF(m.cm, 3);
                tri::UpdateNormal<CMeshO>::PerVertexFromCurrentFaceNormal(m.cm);
                tri::UpdateNormal<CMeshO>::NormalizePerVertex(m.cm);

                for(CMeshO::FaceIterator fi=m.cm.face.begin(); fi!=m.cm.face.end(); fi++) {
                    if((*fi).IsD()) continue;
                    for(int j=0; j<3; j++) {
                        if(ep.qVertTest(face::Pos<CMeshO::FaceType>(&*fi,j))  &&
                           !(*fi).V(j)->IsV() &&
                           (!selected || ((*fi).IsS() && (*fi).FFp(j)->IsS())) ) {
                                double noise;						// noise value
                                Point3m dispDir = (*fi).V(j)->N();	// displacement direction

                                Point3m p = (*fi).V(j)->P() / noiseScale;
                                noise = generateNoiseValue(octaves, p);
                                // only values bigger than noiseClamp will be considered
                                noise = (noise<noiseClamp?0.0:(noise-noiseClamp));

                                // displacement offset
                                Point3m offset = -(dispDir * chipDepth * noise) / dispSteps;

                                (*fi).V(j)->P() += offset;
                                if(faceIntersections(m.cm, face::Pos<CMeshO::FaceType>(&*fi,j), gM))
                                    (*fi).V(j)->P() -= offset;
                                else if(storeDispl)	// store displacement
                                    vah[(*fi).V(j)] = vah[(*fi).V(j)] + offset;

                                // mark as visited (displaced)
                                (*fi).V(j)->SetV();
                            }
                    }
                }
                // clear vertices V bit again
                tri::UpdateFlags<CMeshO>::VertexClearV(m.cm);
            }

            // update normals
            vcg::tri::UpdateNormal<CMeshO>::PerVertexNormalizedPerFace(m.cm);

            smoothPeaks(m.cm, selected, storeDispl);

            // readjust selection
            if(selected) tri::UpdateSelection<CMeshO>::VertexFromFaceLoose(m.cm);

            return true;
}


/* Refines the mesh where needed.
 * In one cases we need to perform additional checks to avoid problems:
 * when we are working on the selection. */
void GeometryAgingPlugin::refineMesh(CMeshO &m, QualityEdgePred &ep, bool selection, vcg::CallBackPos *cb)
{
    bool ref = true;
    CMeshO::FaceIterator fi;

    // allocate selection user bit over faces
    ep.allocateSelBit();

    // clear sel user bit on all faces
    ep.clearSelBit(m);

    while(ref) {
        if(selection) {
            // set selbit bit on selected faces
            for(fi=m.face.begin(); fi!=m.face.end(); fi++)
                if(!(*fi).IsD() && (*fi).IsS()) ep.setFaceSelBit(&*fi);
            // dilate selection
            tri::UpdateSelection<CMeshO>::VertexFromFaceLoose(m);
            tri::UpdateSelection<CMeshO>::FaceFromVertexLoose(m);
        }
        MidPoint<CMeshO> mid(&m);
        ref = tri::RefineE<CMeshO, MidPoint<CMeshO>, QualityEdgePred>(m, mid, ep, selection, cb);
        if(ref) tri::UpdateNormal<CMeshO>::PerFaceNormalized(m);

        if(selection) {
            // erode selection
            tri::UpdateSelection<CMeshO>::VertexFromFaceStrict(m);
            tri::UpdateSelection<CMeshO>::FaceFromVertexStrict(m);
        }

        // clear sel user bit on all faces
        ep.clearSelBit(m);
    }

    // delete the 2 user bits previously allocated
    ep.deallocateSelBit();
}


/* Returns a noise value in range [0,1] using the selected style */
double GeometryAgingPlugin::generateNoiseValue(int Octaves, const CVertexO::CoordType &p)
{
    double noise = .0;
    float freq = 1.0;
    for(int i=0; i<Octaves; i++) {
        noise += math::Perlin::Noise(freq*p.X(), freq*p.Y(), freq*p.Z()) / freq;
        freq *= 2;
    }
    // no negative values allowed (negative noise generates hills, not chips)
    return fabs(noise);
}


/* Tests if the faces incident on the current vertex intersect some other face of the mesh */
bool GeometryAgingPlugin::faceIntersections(CMeshO &m, face::Pos<CMeshO::FaceType> p, GridStaticPtr<CFaceO, CMeshO::ScalarType> &gM)
{
    Box3<CMeshO::ScalarType> bbox;
    std::vector<CFaceO*> inBox;
    face::Pos<CMeshO::FaceType> start(p);

    // look for a border face (if any)
    do
        p.NextE();
    while(p != start && !p.IsBorder());
    start = p;

    do {
        // test current face intersections
        p.f->GetBBox(bbox);
        tri::GetInBoxFace(m, gM, bbox, inBox);
        std::vector<CFaceO*>::iterator fib;
        for(fib=inBox.begin(); fib!=inBox.end(); fib++)
            if(*fib != p.f)
                if(tri::Clean<CMeshO>::TestFaceFaceIntersection(p.f, *fib))
                    return true;
        // move to the next face
        p.NextE();
    } while(p != start && !p.IsBorder());

    return false;
}


/* Smooths higher and thinner peaks (edges whose incident faces form an angle
 * greater than 150 degrees) */
void GeometryAgingPlugin::smoothPeaks(CMeshO &m, bool selected, bool updateErosionAttr)
{
    AngleEdgePred aep = AngleEdgePred(150.0);
    GridStaticPtr<CFaceO, CMeshO::ScalarType> gM;
    gM.Set(m.face.begin(), m.face.end());
    CMeshO::PerVertexAttributeHandle<Point3m> vah =
        vcg::tri::Allocator<CMeshO>::GetPerVertexAttribute<Point3m>(m, "Erosion");

    for(CMeshO::FaceIterator fi=m.face.begin(); fi!=m.face.end(); fi++) {
        if((*fi).IsD()) continue;
        for(int j=0; j<3; j++) {
            if(aep(face::Pos<CMeshO::FaceType>(&*fi,j)) && !(*fi).V(j)->IsV() &&
               (!selected || ((*fi).IsS() && (*fi).FFp(j)->IsS())) ) {
                    Point3m middlepos(((*fi).V2(j)->P() + (*fi).FFp(j)->V2((*fi).FFi(j))->P()) / 2.0);
                    Point3m oldpos = (*fi).V(j)->P();
                    Point3m dirj(((*fi).V(j)->P() - (*fi).V1(j)->P()) / 2.0);
                    (*fi).V(j)->P() = middlepos + dirj;
                    if(faceIntersections(m, face::Pos<CMeshO::FaceType>(&*fi,j), gM))
                        (*fi).V(j)->P() = oldpos;
                    else if(updateErosionAttr)		// update stored displacement
                        vah[(*fi).V(j)] += ((*fi).V(j)->P() - oldpos);
                    (*fi).V(j)->SetV();
            }
        }
    }

    vcg::tri::UpdateNormal<CMeshO>::PerVertexNormalizedPerFace(m);

    // clear vertices V bit again
    tri::UpdateFlags<CMeshO>::VertexClearV(m);
}


/* Compute per vertex quality values using mean curvature */
void GeometryAgingPlugin::computeMeanCurvature(CMeshO &m)
{
    int delvert = tri::Clean<CMeshO>::RemoveUnreferencedVertex(m);
    if(delvert) Log( "Pre-Curvature Cleaning: Removed %d unreferenced vertices", delvert);
    tri::Allocator<CMeshO>::CompactVertexVector(m);
    tri::UpdateCurvature<CMeshO>::MeanAndGaussian(m);
    tri::UpdateQuality<CMeshO>::VertexFromMeanCurvatureHG(m);
}


MESHLAB_PLUGIN_NAME_EXPORTER(GeometryAgingPlugin)
