/*
 *  Copyright (c) 2000-2022 Inria
 *  All rights reserved.
 *
 *  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 following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  * Neither the name of the ALICE Project-Team 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 THE COPYRIGHT HOLDER 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.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

#ifndef GEOGRAM_MESH_MESH_FRAME_FIELD
#define GEOGRAM_MESH_MESH_FRAME_FIELD

#include <geogram/basic/common.h>
#include <geogram/basic/geometry.h>
#include <geogram/points/nn_search.h>

/**
 * \file mesh_frame_field.h
 */

namespace GEO {

    class Mesh;

    /**
     * \brief Represents a 3D frame field, i.e. a function that
     *  associates a 3d orthonormal basis to each point in 3D space.
     */
    class GEOGRAM_API FrameField {
    public:
    /**
     * \brief Constructs a new uninitialized FrameField.
     */
    FrameField() : use_NN_(true) {
    }

    /**
     * \brief Specifies whether a spatial search structure
     *  should be created.
     * \details If a spatial search structure is created, then
     *  the field can be queried at any 3D location using
     *  get_nearest_frame() / get_nearest_frame_index(),
     *  this is the default mode. Otherwise, get_nearest_frame()
     *  and get_nearest_frame_index() cannot be used.
     * \param[in] x true if spatial search should be used, false
     *  otherwise.
     */
    void set_use_spatial_search(bool x) {
        use_NN_ = x;
    }

    /**
     * \brief Loads a frame field from a file.
     * \param[in] M a tetrahedral mesh
     * \param[in] filename name of the file that contains the
     *  frame vectors
     * \param[in] volumetric if true, the frames are attached
     *  to the tets of \p M, else they are attached to the facets
     * \details The file is supposed to be ASCII, with one vector
     *  per line. Alternatively, the frames can be attached to
     *  specified points. In this case, \p volumetric is ignored, and
     *  the file has 12 scalars per line, that correspond to the
     *  coordinates of a point and the three vectors attached to the point.
     * \retval true on success
     * \retval false otherwise
     */
    bool load(
        const Mesh& M, bool volumetric, const std::string& filename
    );


    /**
     * \brief Creates a frame field that matches a given mesh.
     * \details The frames are interpolated from the sharp features
     *  of the mesh.
     * \param[in] M the input mesh
     * \param[in] volumetric if true, the frame field is extrapolated
     *  to the tetrahedra of the mesh (using nearest neighbors for now)
     * \param[in] sharp_angle_threshold angles smaller than this threshold
     *  (in degrees) are considered to be sharp features
     */
    void create_from_surface_mesh(
        const Mesh& M, bool volumetric, double sharp_angle_threshold=45.0
    );

    /**
     * \brief Gets the index of the frame nearest to a given point.
     * \details Cannot be used if set_use_spatial_search(false) was
     *  called.
     * \param[in] p the 3d coordinates of the point
     * \return the index of the frame nearest to \p p
     */
    index_t get_nearest_frame_index(const double* p) const {
        geo_assert(use_NN_);
        return NN_->get_nearest_neighbor(p);
    }

    /**
     * \brief Gets the frame nearest to a given point.
     * \details Cannot be used if set_use_spatial_search(false) was
     *  called.
     * \param[in] p the 3d coordinates of the point
     * \param[out] f the 9 coordinates of the three
     *  vectors that compose the fram
     */
    void get_nearest_frame(const double* p, double* f) const {
        geo_assert(use_NN_);
        index_t fi = get_nearest_frame_index(p);
        for(index_t c = 0; c < 9; ++c) {
            f[c] = frames_[fi * 9 + c];
        }
    }

    /**
     * \brief Gets the vector that contains all the frames
     *   coordinates.
     * \return a const reference to the vector of all the
     *   frame coordinates.
     */
    const vector<double>& frames() const {
        return frames_;
    }

    /*
     * \brief Scales one of the vectors of a frame.
     * \details On exit, the norm of the vector nearest to \p N in
     *   \p frame has a norm equal to \p s.
     * \param[in,out] frame the frame, as an array of 9 doubles
     * \param[in] N the vector to be scaled (retrieved in the frame)
     * \param[in] s scaling factor
     */
    static void scale_frame_vector(double* frame, const vec3& N, double s);

    /**
     * \brief Fixes a frame in such a way that it is orthogonal
     *  to a given vector.
     * \details Makes one of the frame vectors aligned with \p N
     *  and the two other ones orthogonal to N.
     * \param[in,out] frame the frame to fix
     * \param[in] N the normal vector to be preserved
     */
    static void fix_frame(double* frame, const vec3& N);

    private:
    NearestNeighborSearch_var NN_;
    vector<double> frames_;
    vector<double> centers_;
    bool use_NN_;
    };


}

#endif
