//---------------------------------------------------------------------------//
//!
//! \file   MonteCarlo_StandardCompleteDopplerBroadenedPhotonEnergyDistribution.hpp
//! \author Alex Robinson
//! \brief  The standard complete Doppler broadened photon energy dist. decl.
//!
//---------------------------------------------------------------------------//

#ifndef MONTE_CARLO_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_HPP
#define MONTE_CARLO_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_HPP

// Std Lib Includes
#include <memory>

// Boost Includes
#include <boost/bimap.hpp>

// FRENSIE Includes
#include "MonteCarlo_CompleteDopplerBroadenedPhotonEnergyDistribution.hpp"
#include "Data_SubshellType.hpp"
#include "MonteCarlo_ComptonProfileSubshellConverter.hpp"
#include "MonteCarlo_ComptonProfilePolicy.hpp"
#include "Utility_TabularUnivariateDistribution.hpp"

namespace MonteCarlo{

//! The standard complete (all subshells) Doppler broadened photon energy dist.
template<typename ComptonProfilePolicy>
class StandardCompleteDopplerBroadenedPhotonEnergyDistribution : public CompleteDopplerBroadenedPhotonEnergyDistribution
{

public:

  //! The trials counter type
  typedef CompleteDopplerBroadenedPhotonEnergyDistribution::Counter Counter;

  //! Constructor
  StandardCompleteDopplerBroadenedPhotonEnergyDistribution(
               const std::vector<double>& endf_subshell_occupancies,
               const std::vector<Data::SubshellType>& endf_subshell_order,
               const std::shared_ptr<const ComptonProfileSubshellConverter>&
               subshell_converter,
               const ComptonProfileArray& compton_profile_array );

  //! Destructor
  virtual ~StandardCompleteDopplerBroadenedPhotonEnergyDistribution()
  { /* .. */ }

  //! Check if the subshell is valid
  bool isValidSubshell( const Data::SubshellType subshell ) const override;

  //! Return the occupancy of a subshell (default is the ENDF occupancy)
  virtual double getSubshellOccupancy( const Data::SubshellType subshell ) const override;

  //! Evaluate the distribution with electron momentum projection
  virtual double evaluateWithElectronMomentumProjection(
                              const double incoming_energy,
                              const double electron_momentum_projection,
                              const double scattering_angle_cosine ) const override;

  //! Evaluate the exact distribution
  virtual double evaluateExact( const double incoming_energy,
                                const double outgoing_energy,
                                const double scattering_angle_cosine ) const override;

  //! Evaluate the subshell with the electron momentum projection
  double evaluateSubshellWithElectronMomentumProjection(
                                     const double incoming_energy,
                                     const double electron_momentum_projection,
                                     const double scattering_angle_cosine,
                                     const Data::SubshellType subshell ) const override;

  //! Evaluate the exact subshell distribution
  double evaluateSubshellExact( const double incoming_energy,
                                const double outgoing_energy,
                                const double scattering_angle_cosine,
                                const Data::SubshellType subshell ) const override;

  //! Evaluate the PDF with electron momentum projection
  double evaluatePDFWithElectronMomentumProjection(
                                   const double incoming_energy,
                                   const double electron_momentum_projection,
                                   const double scattering_angle_cosine,
                                   const double precision ) const override;

  //! Evaluate the exact PDF
  double evaluatePDFExact( const double incoming_energy,
                           const double outgoing_energy,
                           const double scattering_angle_cosine,
                           const double precision ) const override;

  //! Evaluate the subshell PDF with electron momentum projection
  double evaluateSubshellPDFWithElectronMomentumProjection(
                                     const double incoming_energy,
                                     const double electron_momentum_projection,
                                     const double scattering_angle_cosine,
                                     const Data::SubshellType subshell,
                                     const double precision ) const override;

  //! Evaluate the exact subshell PDF
  double evaluateSubshellPDFExact( const double incoming_energy,
                                   const double outgoing_energy,
                                   const double scattering_angle_cosine,
                                   const Data::SubshellType subshell,
                                   const double precision ) const override;

  //! Evaluate the integrated cross section (b/mu)
  virtual double evaluateIntegratedCrossSection(
                                          const double incoming_energy,
                                          const double scattering_angle_cosine,
                                          const double precision ) const override;

  //! Evaluate the exact integrated cross section (b/mu)
  virtual double evaluateIntegratedCrossSectionExact(
                                          const double incoming_energy,
                                          const double scattering_angle_cosine,
                                          const double precision ) const override;

  //! Evaluate the subshell integrated cross section (b/mu)
  double evaluateSubshellIntegratedCrossSection(
				          const double incoming_energy,
					  const double scattering_angle_cosine,
					  const Data::SubshellType subshell,
					  const double precision ) const override;

  //! Evaluate the exact subshell integrated cross section (b/mu)
  double evaluateSubshellIntegratedCrossSectionExact(
                                          const double incoming_energy,
					  const double scattering_angle_cosine,
					  const Data::SubshellType subshell,
					  const double precision ) const override;

  //! Sample an outgoing energy from the distribution
  void sample( const double incoming_energy,
	       const double scattering_angle_cosine,
	       double& outgoing_energy,
	       Data::SubshellType& shell_of_interaction ) const override;

  //! Sample an outgoing energy and record the number of trials
  void sampleAndRecordTrials( const double incoming_energy,
			      const double scattering_angle_cosine,
			      double& outgoing_energy,
			      Data::SubshellType& shell_of_interaction,
			      Counter& trials ) const override;

  //! Sample an electron momentum from the distribution
  void sampleMomentumAndRecordTrials(
                                    const double incoming_energy,
                                    const double scattering_angle_cosine,
                                    double& electron_momentum,
                                    Data::SubshellType& shell_of_interaction,
                                    Counter& trials ) const override;

  //! Sample an electron momentum from the subshell distribution
  double sampleSubshellMomentum( const double incoming_energy,
                                 const double scattering_angle_cosine,
                                 const Data::SubshellType subshell ) const override;

protected:

  //! Return the old subshell index corresponding to the subshell
  unsigned getOldSubshellIndex( const Data::SubshellType subshell ) const;

  //! Return the endf subshell index corresponding to the subshell
  unsigned getENDFSubshellIndex( const Data::SubshellType subshell ) const;

  //! Return the subshell corresponding to the endf subshell index
  Data::SubshellType getSubshell( const size_t endf_subshell_index ) const;

  //! Return the Compton profile for a subshell
  const ComptonProfile& getComptonProfile( const Data::SubshellType& subshell) const;

  //! Return the Compton profile for an old subshell index
  const ComptonProfile& getComptonProfile(
                                    const unsigned& old_subshell_index ) const;

  //! Sample an ENDF subshell
  Data::SubshellType sampleENDFInteractionSubshell() const;

  //! Sample an interaction subshell
  virtual void sampleInteractionSubshell( size_t& old_subshell_index,
                                          double& subshell_binding_energy,
                                          Data::SubshellType& subshell ) const = 0;

private:

  // Sample an electron momentum from the subshell distribution
  double sampleSubshellMomentum( const double incoming_energy,
                                 const double scattering_angle_cosine,
                                 const double subshell_binding_energy,
                                 const ComptonProfile& compton_profile ) const;

  // The ENDF subshell interaction probabilities
  std::unique_ptr<const Utility::TabularUnivariateDistribution>
  d_endf_subshell_occupancy_distribution;

  // The ENDF subshell order
  typedef boost::bimap<unsigned,Data::SubshellType> SubshellOrderMapType;
  boost::bimap<unsigned,Data::SubshellType> d_endf_subshell_order;

  // The ENDF subshell occupancies
  std::vector<double> d_endf_subshell_occupancies;

  // The Compton profile subshell converter
  std::shared_ptr<const ComptonProfileSubshellConverter> d_subshell_converter;

  // The electron momentum dist array
  ComptonProfileArray d_compton_profile_array;
};

} // end MonteCarlo namespace

//---------------------------------------------------------------------------//
// Template Includes
//---------------------------------------------------------------------------//

#include "MonteCarlo_StandardCompleteDopplerBroadenedPhotonEnergyDistribution_def.hpp"

//---------------------------------------------------------------------------//

#endif // end MONTE_CARLO_STANDARD_COMPLETE_DOPPLER_BROADENED_PHOTON_ENERGY_DISTRIBUTION_HPP

//---------------------------------------------------------------------------//
// end MonteCarlo_StandardCompleteDopplerBroadenedPhotonEnergyDistribution.hpp
//---------------------------------------------------------------------------//


/**
 * @file envelope_generator.cc
 * Computes reverberation envelopes from eigenverbs.
 */
#include <usml/eigenverb/envelope_generator.h>
#include <usml/eigenverb/eigenverb.h>
#include <usml/sensors/beam_pattern_map.h>
#include <usml/sensors/beam_pattern_model.h>
#include <usml/threads/smart_ptr.h>
#include <boost/foreach.hpp>

using namespace usml::eigenverb ;
using namespace usml::sensors ;

/**
 * Minimum intensity level for valid reverberation contributions (dB).
 */
double envelope_generator::intensity_threshold = -300.0 ;

/**
 * Maximum distance between the peaks of the source and receiver eigenverbs.
 */
double envelope_generator::distance_threshold = 6.0 ;

/**
 * The mutex for static properties.
 */
read_write_lock envelope_generator::_property_mutex;

/**
 * Time axis for reverberation calculation.  Defaults to
 * a linear sequence out to 400 sec with a sampling period of 0.1 sec.
 */
unique_ptr<const seq_vector> envelope_generator::_travel_time( new seq_linear(0.0,0.1,400.0) ) ;

/**
 * Copies envelope computation parameters from static memory into
 * this specific task.
 */
envelope_generator::envelope_generator(
	sensor_pair* sensor_pair,
	double initial_time,
	size_t src_freq_first,
	size_t num_azimuths
):
    _done(false),
    _initial_time(initial_time),
    _ocean( ocean_shared::current() ),
    _sensor_pair(sensor_pair),
    _src_eigenverbs(sensor_pair->source()->eigenverbs()),
    _rcv_eigenverbs(sensor_pair->receiver()->eigenverbs()),
    _eigenverb_interpolator(sensor_pair->receiver()->frequencies(),sensor_pair->frequencies())
{
    write_lock_guard guard(_property_mutex);

    // Get source_params for reverb_duration and pulse_length, and beam_list size
    source_params::reference src_params = _sensor_pair->source()->source();
    _src_beam_list = src_params->beam_list();

    // Get receiver_params for beam_list size
    sensor_params::id_type rcv_params_ID = _sensor_pair->receiver()->paramsID();
    receiver_params::reference rcv_params =
        receiver_params_map::instance()->find(rcv_params_ID);

    _rcv_beam_list = rcv_params->beam_list();

    add_envelope_listener(_sensor_pair);

    _envelopes = envelope_collection::reference( new envelope_collection(
    	_sensor_pair->frequencies(),
        src_freq_first,
        _travel_time.get(),
        src_params->reverb_duration(),
        src_params->pulse_length(),
        pow(10.0,intensity_threshold/10.0),
        num_azimuths,
        _src_beam_list.size(),
        _rcv_beam_list.size(),
        _initial_time,
        _sensor_pair->source()->sensorID(),
        _sensor_pair->receiver()->sensorID(),
        _sensor_pair->source()->position(),
        _sensor_pair->receiver()->position() ) ) ;
}

/**
 * Executes the Eigenverb reverberation model.
 */
void envelope_generator::run() {

	// create memory for work products

    const seq_vector* freq = _envelopes->envelope_freq() ;
    const size_t num_freq = freq->size() ;

	vector<double> scatter( num_freq, 1.0 ) ;
	matrix<double> src_beam( num_freq, _envelopes->num_src_beams(), 1.0 ) ;
	matrix<double> rcv_beam( num_freq, _envelopes->num_rcv_beams(), 1.0 ) ;

	eigenverb rcv_verb ;
	rcv_verb.frequencies = freq ;
	rcv_verb.power = vector<double>( num_freq ) ;

	// loop through eigenrays for each interface

	for ( size_t interface=0 ; interface < _rcv_eigenverbs->num_interfaces() ; ++interface) {

		BOOST_FOREACH( eigenverb verb, _rcv_eigenverbs->eigenverbs(interface) ) {
			_eigenverb_interpolator.interpolate(verb,&rcv_verb) ;

			// Cull eigenverbs down with rtree.query
			std::vector<value_pair> result_s;
			_src_eigenverbs->query_rtree(interface, rcv_verb, result_s);

			BOOST_FOREACH( value_pair const& vp, result_s ) {

				eigenverb src_verb = *(vp.second);

				// determine relative range and bearing between the projected Gaussians
				// skip this combo if source peak too far away

			    double bearing ;
			    const double range = rcv_verb.position.gc_range( src_verb.position, &bearing ) ;
			    if ( range > distance_threshold * max(rcv_verb.length,rcv_verb.width)) continue ;

			    if ( range < 1e-6 ) bearing = 0 ;	// fixes bearing = NaN
			    bearing -= rcv_verb.direction ;		// relative bearing

			    const double ys = range * cos( bearing ) ;
			    const double ys2 = ys * ys ;
			    if ( abs(ys) > distance_threshold * rcv_verb.length ) continue ;

			    const double xs = range * sin( bearing ) ;
			    const double xs2 = xs * xs ;
			    if ( abs(xs) > distance_threshold * rcv_verb.width ) continue ;

				// compute interface scattering strength
			    // skip this combo if scattering strength is trivial

				if ( ! scattering( interface,
					 rcv_verb.position, *freq,
					 src_verb.grazing, rcv_verb.grazing,
					 src_verb.direction, rcv_verb.direction,
					 &scatter ) ) continue ;

				// compute beam levels

				src_beam = beam_gain(_src_beam_list, freq, src_verb.source_de,
					src_verb.source_az, _sensor_pair->source()->orient());
				rcv_beam = beam_gain(_rcv_beam_list, freq, rcv_verb.source_de,
					rcv_verb.source_az, _sensor_pair->receiver()->orient());

				// create envelope contribution

				_envelopes->add_contribution( src_verb, rcv_verb,
						src_beam, rcv_beam, scatter, xs2, ys2 ) ;
			}
		}
	}
	this->notify_envelope_listeners(_envelopes) ;
}

/**
 * Computes the beam_gain
 */
matrix<double> envelope_generator::beam_gain(
    sensor_params::beam_pattern_list beam_list,
    const seq_vector* freq, double de_rad, double az_rad, orientation orient)
{
    const vector<double> frequencies = *freq;	// beam_level requires ublas vector
    matrix<double> beam_matrix( freq->size(), beam_list.size() ) ;
    vector<double> level( freq->size(), 0.0 ) ;
    BOOST_FOREACH( beam_pattern_model::id_type id, beam_list) {
        beam_pattern_model::reference bp = beam_pattern_map::instance()->find(id);
        bp->beam_level(de_rad, az_rad, orient, frequencies, &level ) ;

        std::copy(level.begin(), level.end(), beam_matrix.begin1());
    }
    return beam_matrix;
}

/**
 * Computes the broadband scattering strength for a specific interface.
 */
bool envelope_generator::scattering( size_t interface, const wposition1& location,
		const seq_vector& frequencies, double de_incident,
		double de_scattered, double az_incident, double az_scattered,
		vector<double>* amplitude)
{
	switch ( interface ) {
	case eigenverb::BOTTOM:
		_ocean->bottom().scattering(location,frequencies,
				de_incident,de_scattered,az_incident,az_scattered,
				amplitude) ;
		break;
	case eigenverb::SURFACE:
		_ocean->surface().scattering(location,frequencies,
				de_incident,de_scattered,az_incident,az_scattered,
				amplitude) ;
		break;
	default:
		size_t layer = (size_t) floor( (interface-2.0)/2.0 ) ;
		_ocean->volume( layer ).scattering(location,frequencies,
				de_incident,de_scattered,az_incident,az_scattered,
				amplitude) ;
		break;
	}

	// check to see that scattering strenght is not trivial

	BOOST_FOREACH (double amp, *amplitude){
        if (amp >= intensity_threshold ) {
            return true;
        }
    }
    return true;
}


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_PROJECTIONS_LAEA_HPP
#define BOOST_GEOMETRY_PROJECTIONS_LAEA_HPP

#include <boost/config.hpp>
#include <boost/geometry/util/math.hpp>
#include <boost/math/special_functions/hypot.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_auth.hpp>
#include <boost/geometry/srs/projections/impl/pj_qsfn.hpp>

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace laea
    {
            static const double epsilon10 = 1.e-10;

            enum mode_type {
                n_pole = 0,
                s_pole = 1,
                equit  = 2,
                obliq  = 3
            };

            template <typename T>
            struct par_laea
            {
                T   sinb1;
                T   cosb1;
                T   xmf;
                T   ymf;
                T   mmf;
                T   qp;
                T   dd;
                T   rq;
                detail::apa<T> apa;
                mode_type mode;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_laea_ellipsoid
                : public base_t_fi<base_laea_ellipsoid<T, Parameters>, T, Parameters>
            {
                par_laea<T> m_proj_parm;

                inline base_laea_ellipsoid(const Parameters& par)
                    : base_t_fi<base_laea_ellipsoid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(e_forward)  ellipsoid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    static const T half_pi = detail::half_pi<T>();

                    T coslam, sinlam, sinphi, q, sinb=0.0, cosb=0.0, b=0.0;

                    coslam = cos(lp_lon);
                    sinlam = sin(lp_lon);
                    sinphi = sin(lp_lat);
                    q = pj_qsfn(sinphi, this->m_par.e, this->m_par.one_es);

                    if (this->m_proj_parm.mode == obliq || this->m_proj_parm.mode == equit) {
                        sinb = q / this->m_proj_parm.qp;
                        cosb = sqrt(1. - sinb * sinb);
                    }

                    switch (this->m_proj_parm.mode) {
                    case obliq:
                        b = 1. + this->m_proj_parm.sinb1 * sinb + this->m_proj_parm.cosb1 * cosb * coslam;
                        break;
                    case equit:
                        b = 1. + cosb * coslam;
                        break;
                    case n_pole:
                        b = half_pi + lp_lat;
                        q = this->m_proj_parm.qp - q;
                        break;
                    case s_pole:
                        b = lp_lat - half_pi;
                        q = this->m_proj_parm.qp + q;
                        break;
                    }
                    if (fabs(b) < epsilon10) {
                        BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                    }

                    switch (this->m_proj_parm.mode) {
                    case obliq:
                        b = sqrt(2. / b);
                        xy_y = this->m_proj_parm.ymf * b * (this->m_proj_parm.cosb1 * sinb - this->m_proj_parm.sinb1 * cosb * coslam);
                        goto eqcon;
                        break;
                    case equit:
                        b = sqrt(2. / (1. + cosb * coslam));
                        xy_y = b * sinb * this->m_proj_parm.ymf;
                eqcon:
                        xy_x = this->m_proj_parm.xmf * b * cosb * sinlam;
                        break;
                    case n_pole:
                    case s_pole:
                        if (q >= 0.) {
                            b = sqrt(q);
                            xy_x = b * sinlam;
                            xy_y = coslam * (this->m_proj_parm.mode == s_pole ? b : -b);
                        } else
                            xy_x = xy_y = 0.;
                        break;
                    }
                }

                // INVERSE(e_inverse)  ellipsoid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T xy_x, T xy_y, T& lp_lon, T& lp_lat) const
                {
                    T cCe, sCe, q, rho, ab=0.0;

                    switch (this->m_proj_parm.mode) {
                    case equit:
                    case obliq:
                        xy_x /= this->m_proj_parm.dd;
                        xy_y *=  this->m_proj_parm.dd;
                        rho = boost::math::hypot(xy_x, xy_y);
                        if (rho < epsilon10) {
                            lp_lon = 0.;
                            lp_lat = this->m_par.phi0;
                            return;
                        }
                        sCe = 2. * asin(.5 * rho / this->m_proj_parm.rq);
                        cCe = cos(sCe);
                        sCe = sin(sCe);
                        xy_x *= sCe;
                        if (this->m_proj_parm.mode == obliq) {
                            ab = cCe * this->m_proj_parm.sinb1 + xy_y * sCe * this->m_proj_parm.cosb1 / rho;
                            xy_y = rho * this->m_proj_parm.cosb1 * cCe - xy_y * this->m_proj_parm.sinb1 * sCe;
                        } else {
                            ab = xy_y * sCe / rho;
                            xy_y = rho * cCe;
                        }
                        break;
                    case n_pole:
                        xy_y = -xy_y;
                        BOOST_FALLTHROUGH;
                    case s_pole:
                        q = (xy_x * xy_x + xy_y * xy_y);
                        if (q == 0.0) {
                            lp_lon = 0.;
                            lp_lat = this->m_par.phi0;
                            return;
                        }
                        ab = 1. - q / this->m_proj_parm.qp;
                        if (this->m_proj_parm.mode == s_pole)
                            ab = - ab;
                        break;
                    }
                    lp_lon = atan2(xy_x, xy_y);
                    lp_lat = pj_authlat(asin(ab), this->m_proj_parm.apa);
                }

                static inline std::string get_name()
                {
                    return "laea_ellipsoid";
                }

            };

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_laea_spheroid
                : public base_t_fi<base_laea_spheroid<T, Parameters>, T, Parameters>
            {
                par_laea<T> m_proj_parm;

                inline base_laea_spheroid(const Parameters& par)
                    : base_t_fi<base_laea_spheroid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    static const T fourth_pi = detail::fourth_pi<T>();

                    T  coslam, cosphi, sinphi;

                    sinphi = sin(lp_lat);
                    cosphi = cos(lp_lat);
                    coslam = cos(lp_lon);
                    switch (this->m_proj_parm.mode) {
                    case equit:
                        xy_y = 1. + cosphi * coslam;
                        goto oblcon;
                    case obliq:
                        xy_y = 1. + this->m_proj_parm.sinb1 * sinphi + this->m_proj_parm.cosb1 * cosphi * coslam;
                oblcon:
                        if (xy_y <= epsilon10) {
                            BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                        }
                        xy_y = sqrt(2. / xy_y);
                        xy_x = xy_y * cosphi * sin(lp_lon);
                        xy_y *= this->m_proj_parm.mode == equit ? sinphi :
                           this->m_proj_parm.cosb1 * sinphi - this->m_proj_parm.sinb1 * cosphi * coslam;
                        break;
                    case n_pole:
                        coslam = -coslam;
                        BOOST_FALLTHROUGH;
                    case s_pole:
                        if (fabs(lp_lat + this->m_par.phi0) < epsilon10) {
                            BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                        }
                        xy_y = fourth_pi - lp_lat * .5;
                        xy_y = 2. * (this->m_proj_parm.mode == s_pole ? cos(xy_y) : sin(xy_y));
                        xy_x = xy_y * sin(lp_lon);
                        xy_y *= coslam;
                        break;
                    }
                }

                // INVERSE(s_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T xy_x, T xy_y, T& lp_lon, T& lp_lat) const
                {
                    static const T half_pi = detail::half_pi<T>();

                    T  cosz=0.0, rh, sinz=0.0;

                    rh = boost::math::hypot(xy_x, xy_y);
                    if ((lp_lat = rh * .5 ) > 1.) {
                        BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                    }
                    lp_lat = 2. * asin(lp_lat);
                    if (this->m_proj_parm.mode == obliq || this->m_proj_parm.mode == equit) {
                        sinz = sin(lp_lat);
                        cosz = cos(lp_lat);
                    }
                    switch (this->m_proj_parm.mode) {
                    case equit:
                        lp_lat = fabs(rh) <= epsilon10 ? 0. : asin(xy_y * sinz / rh);
                        xy_x *= sinz;
                        xy_y = cosz * rh;
                        break;
                    case obliq:
                        lp_lat = fabs(rh) <= epsilon10 ? this->m_par.phi0 :
                           asin(cosz * this->m_proj_parm.sinb1 + xy_y * sinz * this->m_proj_parm.cosb1 / rh);
                        xy_x *= sinz * this->m_proj_parm.cosb1;
                        xy_y = (cosz - sin(lp_lat) * this->m_proj_parm.sinb1) * rh;
                        break;
                    case n_pole:
                        xy_y = -xy_y;
                        lp_lat = half_pi - lp_lat;
                        break;
                    case s_pole:
                        lp_lat -= half_pi;
                        break;
                    }
                    lp_lon = (xy_y == 0. && (this->m_proj_parm.mode == equit || this->m_proj_parm.mode == obliq)) ?
                        0. : atan2(xy_x, xy_y);
                }

                static inline std::string get_name()
                {
                    return "laea_spheroid";
                }

            };

            // Lambert Azimuthal Equal Area
            template <typename Parameters, typename T>
            inline void setup_laea(Parameters& par, par_laea<T>& proj_parm)
            {
                static const T half_pi = detail::half_pi<T>();

                T t;

                t = fabs(par.phi0);
                if (fabs(t - half_pi) < epsilon10)
                    proj_parm.mode = par.phi0 < 0. ? s_pole : n_pole;
                else if (fabs(t) < epsilon10)
                    proj_parm.mode = equit;
                else
                    proj_parm.mode = obliq;
                if (par.es != 0.0) {
                    double sinphi;

                    par.e = sqrt(par.es);
                    proj_parm.qp = pj_qsfn(1., par.e, par.one_es);
                    proj_parm.mmf = .5 / (1. - par.es);
                    proj_parm.apa = pj_authset<T>(par.es);
                    switch (proj_parm.mode) {
                    case n_pole:
                    case s_pole:
                        proj_parm.dd = 1.;
                        break;
                    case equit:
                        proj_parm.dd = 1. / (proj_parm.rq = sqrt(.5 * proj_parm.qp));
                        proj_parm.xmf = 1.;
                        proj_parm.ymf = .5 * proj_parm.qp;
                        break;
                    case obliq:
                        proj_parm.rq = sqrt(.5 * proj_parm.qp);
                        sinphi = sin(par.phi0);
                        proj_parm.sinb1 = pj_qsfn(sinphi, par.e, par.one_es) / proj_parm.qp;
                        proj_parm.cosb1 = sqrt(1. - proj_parm.sinb1 * proj_parm.sinb1);
                        proj_parm.dd = cos(par.phi0) / (sqrt(1. - par.es * sinphi * sinphi) *
                           proj_parm.rq * proj_parm.cosb1);
                        proj_parm.ymf = (proj_parm.xmf = proj_parm.rq) / proj_parm.dd;
                        proj_parm.xmf *= proj_parm.dd;
                        break;
                    }
                } else {
                    if (proj_parm.mode == obliq) {
                        proj_parm.sinb1 = sin(par.phi0);
                        proj_parm.cosb1 = cos(par.phi0);
                    }
                }
            }

    }} // namespace laea
    #endif // doxygen

    /*!
        \brief Lambert Azimuthal Equal Area projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Azimuthal
         - Spheroid
         - Ellipsoid
        \par Example
        \image html ex_laea.gif
    */
    template <typename T, typename Parameters>
    struct laea_ellipsoid : public detail::laea::base_laea_ellipsoid<T, Parameters>
    {
        template <typename Params>
        inline laea_ellipsoid(Params const& , Parameters const& par)
            : detail::laea::base_laea_ellipsoid<T, Parameters>(par)
        {
            detail::laea::setup_laea(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Lambert Azimuthal Equal Area projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Azimuthal
         - Spheroid
         - Ellipsoid
        \par Example
        \image html ex_laea.gif
    */
    template <typename T, typename Parameters>
    struct laea_spheroid : public detail::laea::base_laea_spheroid<T, Parameters>
    {
        template <typename Params>
        inline laea_spheroid(Params const& , Parameters const& par)
            : detail::laea::base_laea_spheroid<T, Parameters>(par)
        {
            detail::laea::setup_laea(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_laea, laea_spheroid, laea_ellipsoid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI2(laea_entry, laea_spheroid, laea_ellipsoid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(laea_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(laea, laea_entry)
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_LAEA_HPP


/*
 * Copyright (c) 2021 Elastos Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "Mnemonic.h"
#include <utf8proc.h>

#include <Common/hash.h>
#include <Common/ErrorChecker.h>
#include <Common/uint256.h>
#include <Common/Utils.h>

#include <sstream>
#include <boost/algorithm/string.hpp>

namespace fs = boost::filesystem;

namespace Elastos {
    namespace ElaWallet {

        Mnemonic::Mnemonic() {
        }

        WordList Mnemonic::Create(const bytes_t &entropy, const Dictionary &lexicon) {
            if ((entropy.size() % MnemonicSeedMultiple) != 0)
                return {};

            const size_t entropyBits = (entropy.size() * ByteBits);
            const size_t checkBits = (entropyBits / EntropyBitDivisor);
            const size_t totalBits = (entropyBits + checkBits);
            const size_t wordCount = (totalBits / BitsPerWord);

            if ((totalBits % BitsPerWord) != 0 || (wordCount % MnemonicWordMultiple) != 0)
                return {};

            bytes_t data = entropy + sha256(entropy);

            size_t bit = 0;
            WordList words;

            for (size_t word = 0; word < wordCount; word++) {
                size_t position = 0;
                for (size_t loop = 0; loop < BitsPerWord; loop++) {
                    bit = (word * BitsPerWord + loop);
                    position <<= 1;

                    const auto byte = bit / ByteBits;

                    if ((data[byte] & Bip39Shift(bit)) > 0)
                        position++;
                }

                if (position >= DictionarySize)
                    return {};
                words.push_back(lexicon[position]);
            }

            if (words.size() != ((bit + 1) / BitsPerWord))
                return {};

            return words;
        }

        bytes_t Mnemonic::Entropy(const WordList &words, const Dictionary &lexicon) {
            const auto wordCount = words.size();
            if ((wordCount % MnemonicWordMultiple) != 0)
                return {};

            const auto totalBits = BitsPerWord * wordCount;
            const auto checkBits = totalBits / (EntropyBitDivisor + 1);
            const auto entropyBits = totalBits - checkBits;

            if ((entropyBits % ByteBits) != 0)
                return {};

            size_t bit = 0;
            bytes_t data((totalBits + ByteBits - 1) / ByteBits, 0);

            for (const auto& word: words) {
                const auto position = FindPosition(lexicon, word);
                if (position == -1)
                    return {};

                for (size_t loop = 0; loop < BitsPerWord; loop++, bit++)
                {
                    if (position & (1 << (BitsPerWord - loop - 1)))
                    {
                        const auto byte = bit / ByteBits;
                        data[byte] |= Bip39Shift(bit);
                    }
                }
            }

            data.resize(entropyBits / ByteBits);
            return data;
        }

        bool Mnemonic::Validate(const WordList &words, const Dictionary &lexicon) {
            bytes_t entropy = Entropy(words, lexicon);
            if (entropy.empty())
                return false;

            const auto mnemonic = Create(entropy, lexicon);
            return std::equal(mnemonic.begin(), mnemonic.end(), words.begin());
        }

        bool Mnemonic::Validate(const WordList &words, const DictionaryMap &lexicons) {
            for (const auto &lexicon : lexicons) {
                if (Validate(words, *lexicon.second))
                    return true;
            }

            return false;
        }

        std::string Mnemonic::Create(const std::string &language, WordCount wordCount) {
            std::string languageLowerCase = language;
            size_t entropyBits = 0;

            switch (wordCount) {
                case WORDS_12: entropyBits = 128; break;
                case WORDS_15: entropyBits = 160; break;
                case WORDS_18: entropyBits = 192; break;
                case WORDS_21: entropyBits = 224; break;
                case WORDS_24: entropyBits = 256; break;
                default:
                    ErrorChecker::ThrowParamException(Error::InvalidMnemonicWordCount, "invalid mnemonic word count");
            }

            std::transform(languageLowerCase.begin(), languageLowerCase.end(), languageLowerCase.begin(), ::tolower);
            bytes_t entropy = Utils::GetRandom(entropyBits / 8);

            auto it = Language::All.find(languageLowerCase);
            if (it == Language::All.end())
                ErrorChecker::ThrowParamException(Error::InvalidArgument, "invalid mnemonic language");

            WordList words = Create(entropy, *it->second);
            return boost::algorithm::join(words, " ");
        }

        bytes_t Mnemonic::Entropy(const std::string &mnemonic, const Dictionary &lexicon) {
            utf8proc_uint8_t *mnemonicTmp = utf8proc_NFKD((const utf8proc_uint8_t *)mnemonic.c_str());
            std::string mnemonicFixed((char *)mnemonicTmp);
            free(mnemonicTmp);

            WordList words;
            boost::algorithm::split(words, mnemonicFixed, boost::is_any_of(" \n\r\t"), boost::token_compress_on);
            words.erase(std::remove(words.begin(), words.end(), ""), words.end());

            return Entropy(words, lexicon);
        }

        bool Mnemonic::Validate(const std::string &mnemonic) {
            utf8proc_uint8_t *mnemonicTmp = utf8proc_NFKD((const utf8proc_uint8_t *)mnemonic.c_str());
            std::string mnemonicFixed((char *)mnemonicTmp);
            free(mnemonicTmp);

            WordList words;
            boost::algorithm::split(words, mnemonicFixed, boost::is_any_of(" \n\r\t"), boost::token_compress_on);
            words.erase(std::remove(words.begin(), words.end(), ""), words.end());

            return Validate(words);
        }

        uint512 Mnemonic::DeriveSeed(const std::string &mnemonic, const std::string &passphrase) {
            utf8proc_uint8_t *mnemonicTmp = utf8proc_NFKD((const utf8proc_uint8_t *)mnemonic.c_str());
            std::string mnemonicFixed((char *)mnemonicTmp);
            free(mnemonicTmp);

            utf8proc_uint8_t *passphraseTmp = utf8proc_NFKD((const utf8proc_uint8_t *)passphrase.c_str());
            std::string passphraseFixed((char *)passphraseTmp);
            free(passphraseTmp);

            WordList words;
            boost::algorithm::split(words, mnemonicFixed, boost::is_any_of(" \n\r\t"), boost::token_compress_on);
            words.erase(std::remove(words.begin(), words.end(), ""), words.end());

            ErrorChecker::CheckLogic(!Validate(words), Error::Mnemonic, "invalid mnemonic");

            std::string sentence = boost::algorithm::join(words, " ");
            std::string salt = "mnemonic" + passphraseFixed;

            return PBKDF2(bytes_t(sentence.data(), sentence.size()), bytes_t(salt.data(), salt.size()), 2048);
        }

        uint512 Mnemonic::PBKDF2(const bytes_t &pw, const bytes_t &salt, unsigned int rounds) {
            bytes_t s(salt.size() + sizeof(uint32_t));
            uint32_t i, j;
            uint512 key;
            size_t length, keyLen = key.size();
            bytes_t U, T, k;

            assert(rounds > 0);

            memcpy(s.data(), salt.data(), salt.size());

            for (i = 0; keyLen > 0; i++) {
                s[salt.size() + 0] = (uint8_t)(((i + 1) >> 24) & 0xff);
                s[salt.size() + 1] = (uint8_t)(((i + 1) >> 16) & 0xff);
                s[salt.size() + 2] = (uint8_t)(((i + 1) >> 8) & 0xff);
                s[salt.size() + 3] = (uint8_t)((i + 1) & 0xff);

                U = hmac_sha512(pw, s); // U1 = hmac_hash(pw, salt || be32(i))
                T = U;

                for (unsigned int r = 1; r < rounds; r++) {
                    U = hmac_sha512(pw, U); // Urounds = hmac_hash(pw, Urounds-1)
                    for (j = 0; j < T.size(); j++) T[j] ^= U[j]; // Ti = U1 ^ U2 ^ ... ^ Urounds
                }

                // dk = T1 || T2 || ... || Tdklen/hlen
                length = keyLen < T.size() ? keyLen : T.size();
                k += T;
                keyLen -= length;
            }

            key = uint512(k);

            s.clean();
            U.clean();
            T.clean();
            k.clean();

            return key;
        }

    }
}


#ifndef STAN_MCMC_HMC_UNIFORM_BASE_STATIC_UNIFORM_HPP
#define STAN_MCMC_HMC_UNIFORM_BASE_STATIC_UNIFORM_HPP

#include <stan/services/callbacks/logger.hpp>
#include "stan/algorithms/hmc/base_hmc.hpp"
#include "stan/algorithms/hmc/hamiltonians/ps_point.hpp"
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <cmath>
#include <limits>
#include <string>
#include <vector>

namespace stan {

  namespace mcmc {
    /**
     * Hamiltonian Monte Carlo implementation that uniformly samples
     * from trajectories with a static integration time
     */
    template <class Model,
              template<class, class> class Hamiltonian,
              template<class> class Integrator,
              class BaseRNG>
    class base_static_uniform:
      public base_hmc<Model, Hamiltonian, Integrator, BaseRNG> {
    public:
      base_static_uniform(const Model& model, BaseRNG& rng)
        : base_hmc<Model, Hamiltonian, Integrator, BaseRNG>(model, rng),
          T_(1), energy_(0) {
        update_L_();
      }

      ~base_static_uniform() {}

      sample
      transition(sample& init_sample, callbacks::logger& logger) {
        this->sample_stepsize();

        this->seed(init_sample.cont_params());

        this->hamiltonian_.sample_p(this->z_, this->rand_int_);
        this->hamiltonian_.init(this->z_, logger);

        ps_point z_init(this->z_);
        double H0 = this->hamiltonian_.H(this->z_);

        ps_point z_sample(this->z_);
        double sum_prob = 1;
        double sum_metro_prob = 1;

        boost::random::uniform_int_distribution<> uniform(0, L_ - 1);
        int Lp = uniform(this->rand_int_);

        for (int l = 0; l < Lp; ++l) {
          this->integrator_.evolve(this->z_, this->hamiltonian_,
                                   -this->epsilon_,
                                   logger);

          double h = this->hamiltonian_.H(this->z_);
          if (boost::math::isnan(h))
            h = std::numeric_limits<double>::infinity();

          double prob = std::exp(H0 - h);
          sum_prob += prob;
          sum_metro_prob += prob > 1 ? 1 : prob;

          if (this->rand_uniform_() < prob / sum_prob)
            z_sample = this->z_;
        }

        this->z_.ps_point::operator=(z_init);

        for (int l = 0; l < L_ - 1 - Lp; ++l) {
          this->integrator_.evolve(this->z_, this->hamiltonian_,
                                   this->epsilon_,
                                   logger);

          double h = this->hamiltonian_.H(this->z_);
          if (boost::math::isnan(h))
            h = std::numeric_limits<double>::infinity();

          double prob = std::exp(H0 - h);
          sum_prob += prob;
          sum_metro_prob += prob > 1 ? 1 : prob;

          if (this->rand_uniform_() < prob / sum_prob)
            z_sample = this->z_;
        }

        double accept_prob = sum_metro_prob / static_cast<double>(L_);

        this->z_.ps_point::operator=(z_sample);
        this->energy_ = this->hamiltonian_.H(this->z_);
        return sample(this->z_.q,
                      - this->hamiltonian_.V(this->z_),
                      accept_prob);
      }

      void get_sampler_param_names(std::vector<std::string>& names) {
        names.push_back("stepsize__");
        names.push_back("int_time__");
        names.push_back("energy__");
      }

      void get_sampler_params(std::vector<double>& values) {
        values.push_back(this->epsilon_);
        values.push_back(this->T_);
        values.push_back(this->energy_);
      }

      void set_nominal_stepsize_and_T(const double e, const double t) {
        if (e > 0 && t > 0) {
          this->nom_epsilon_ = e;
          T_ = t;
          update_L_();
        }
      }

      void set_nominal_stepsize_and_L(const double e, const int l) {
        if (e > 0 && l > 0) {
          this->nom_epsilon_ = e;
          L_ = l;
          T_ = this->nom_epsilon_ * L_; }
      }

      void set_T(const double t) {
        if (t > 0) {
          T_ = t;
          update_L_();
        }
      }

      void set_nominal_stepsize(const double e) {
        if (e > 0) {
          this->nom_epsilon_ = e;
          update_L_();
        }
      }

      double get_T() {
        return this->T_;
      }

      int get_L() {
        return this->L_;
      }

    protected:
      double T_;
      int L_;
      double energy_;

      void update_L_() {
        L_ = static_cast<int>(T_ / this->nom_epsilon_);
        L_ = L_ < 1 ? 1 : L_;
      }
    };
  }  // mcmc
}  // stan
#endif


// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018-2021 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ----------------------------------------------------------------------------

#include <Eigen/Dense>
#include <iostream>
#include <list>

#include "open3d/geometry/IntersectionTest.h"
#include "open3d/geometry/KDTreeFlann.h"
#include "open3d/geometry/PointCloud.h"
#include "open3d/geometry/TriangleMesh.h"
#include "open3d/utility/Logging.h"

namespace open3d {
namespace geometry {

class BallPivotingVertex;
class BallPivotingEdge;
class BallPivotingTriangle;

typedef BallPivotingVertex* BallPivotingVertexPtr;
typedef std::shared_ptr<BallPivotingEdge> BallPivotingEdgePtr;
typedef std::shared_ptr<BallPivotingTriangle> BallPivotingTrianglePtr;

class BallPivotingVertex {
public:
    enum Type { Orphan = 0, Front = 1, Inner = 2 };

    BallPivotingVertex(int idx,
                       const Eigen::Vector3d& point,
                       const Eigen::Vector3d& normal)
        : idx_(idx), point_(point), normal_(normal), type_(Orphan) {}

    void UpdateType();

public:
    int idx_;
    const Eigen::Vector3d& point_;
    const Eigen::Vector3d& normal_;
    std::unordered_set<BallPivotingEdgePtr> edges_;
    Type type_;
};

class BallPivotingEdge {
public:
    enum Type { Border = 0, Front = 1, Inner = 2 };

    BallPivotingEdge(BallPivotingVertexPtr source, BallPivotingVertexPtr target)
        : source_(source), target_(target), type_(Type::Front) {}

    void AddAdjacentTriangle(BallPivotingTrianglePtr triangle);
    BallPivotingVertexPtr GetOppositeVertex();

public:
    BallPivotingVertexPtr source_;
    BallPivotingVertexPtr target_;
    BallPivotingTrianglePtr triangle0_;
    BallPivotingTrianglePtr triangle1_;
    Type type_;
};

class BallPivotingTriangle {
public:
    BallPivotingTriangle(BallPivotingVertexPtr vert0,
                         BallPivotingVertexPtr vert1,
                         BallPivotingVertexPtr vert2,
                         Eigen::Vector3d ball_center)
        : vert0_(vert0),
          vert1_(vert1),
          vert2_(vert2),
          ball_center_(ball_center) {}

public:
    BallPivotingVertexPtr vert0_;
    BallPivotingVertexPtr vert1_;
    BallPivotingVertexPtr vert2_;
    Eigen::Vector3d ball_center_;
};

void BallPivotingVertex::UpdateType() {
    if (edges_.empty()) {
        type_ = Type::Orphan;
    } else {
        for (const BallPivotingEdgePtr& edge : edges_) {
            if (edge->type_ != BallPivotingEdge::Type::Inner) {
                type_ = Type::Front;
                return;
            }
        }
        type_ = Type::Inner;
    }
}

void BallPivotingEdge::AddAdjacentTriangle(BallPivotingTrianglePtr triangle) {
    if (triangle != triangle0_ && triangle != triangle1_) {
        if (triangle0_ == nullptr) {
            triangle0_ = triangle;
            type_ = Type::Front;
            // update orientation
            if (BallPivotingVertexPtr opp = GetOppositeVertex()) {
                Eigen::Vector3d tr_norm =
                        (target_->point_ - source_->point_)
                                .cross(opp->point_ - source_->point_);
                tr_norm /= tr_norm.norm();
                Eigen::Vector3d pt_norm =
                        source_->normal_ + target_->normal_ + opp->normal_;
                pt_norm /= pt_norm.norm();
                if (pt_norm.dot(tr_norm) < 0) {
                    std::swap(target_, source_);
                }
            } else {
                utility::LogError("GetOppositeVertex() returns nullptr.");
            }
        } else if (triangle1_ == nullptr) {
            triangle1_ = triangle;
            type_ = Type::Inner;
        } else {
            utility::LogDebug("!!! This case should not happen");
        }
    }
}

BallPivotingVertexPtr BallPivotingEdge::GetOppositeVertex() {
    if (triangle0_ != nullptr) {
        if (triangle0_->vert0_->idx_ != source_->idx_ &&
            triangle0_->vert0_->idx_ != target_->idx_) {
            return triangle0_->vert0_;
        } else if (triangle0_->vert1_->idx_ != source_->idx_ &&
                   triangle0_->vert1_->idx_ != target_->idx_) {
            return triangle0_->vert1_;
        } else {
            return triangle0_->vert2_;
        }
    } else {
        return nullptr;
    }
}

class BallPivoting {
public:
    BallPivoting(const PointCloud& pcd)
        : has_normals_(pcd.HasNormals()), kdtree_(pcd) {
        mesh_ = std::make_shared<TriangleMesh>();
        mesh_->vertices_ = pcd.points_;
        mesh_->vertex_normals_ = pcd.normals_;
        mesh_->vertex_colors_ = pcd.colors_;
        for (size_t vidx = 0; vidx < pcd.points_.size(); ++vidx) {
            vertices.emplace_back(new BallPivotingVertex(static_cast<int>(vidx),
                                                         pcd.points_[vidx],
                                                         pcd.normals_[vidx]));
        }
    }

    virtual ~BallPivoting() {
        for (auto vert : vertices) {
            delete vert;
        }
    }

    bool ComputeBallCenter(int vidx1,
                           int vidx2,
                           int vidx3,
                           double radius,
                           Eigen::Vector3d& center) {
        const Eigen::Vector3d& v1 = vertices[vidx1]->point_;
        const Eigen::Vector3d& v2 = vertices[vidx2]->point_;
        const Eigen::Vector3d& v3 = vertices[vidx3]->point_;
        double c = (v2 - v1).squaredNorm();
        double b = (v1 - v3).squaredNorm();
        double a = (v3 - v2).squaredNorm();

        double alpha = a * (b + c - a);
        double beta = b * (a + c - b);
        double gamma = c * (a + b - c);
        double abg = alpha + beta + gamma;

        if (abg < 1e-16) {
            return false;
        }

        alpha = alpha / abg;
        beta = beta / abg;
        gamma = gamma / abg;

        Eigen::Vector3d circ_center = alpha * v1 + beta * v2 + gamma * v3;
        double circ_radius2 = a * b * c;

        a = std::sqrt(a);
        b = std::sqrt(b);
        c = std::sqrt(c);
        circ_radius2 = circ_radius2 /
                       ((a + b + c) * (b + c - a) * (c + a - b) * (a + b - c));

        double height = radius * radius - circ_radius2;
        if (height >= 0.0) {
            Eigen::Vector3d tr_norm = (v2 - v1).cross(v3 - v1);
            tr_norm /= tr_norm.norm();
            Eigen::Vector3d pt_norm = vertices[vidx1]->normal_ +
                                      vertices[vidx2]->normal_ +
                                      vertices[vidx3]->normal_;
            pt_norm /= pt_norm.norm();
            if (tr_norm.dot(pt_norm) < 0) {
                tr_norm *= -1;
            }

            height = sqrt(height);
            center = circ_center + height * tr_norm;
            return true;
        }
        return false;
    }

    BallPivotingEdgePtr GetLinkingEdge(const BallPivotingVertexPtr& v0,
                                       const BallPivotingVertexPtr& v1) {
        for (BallPivotingEdgePtr edge0 : v0->edges_) {
            for (BallPivotingEdgePtr edge1 : v1->edges_) {
                if (edge0->source_->idx_ == edge1->source_->idx_ &&
                    edge0->target_->idx_ == edge1->target_->idx_) {
                    return edge0;
                }
            }
        }
        return nullptr;
    }

    void CreateTriangle(const BallPivotingVertexPtr& v0,
                        const BallPivotingVertexPtr& v1,
                        const BallPivotingVertexPtr& v2,
                        const Eigen::Vector3d& center) {
        utility::LogDebug(
                "[CreateTriangle] with v0.idx={}, v1.idx={}, v2.idx={}",
                v0->idx_, v1->idx_, v2->idx_);
        BallPivotingTrianglePtr triangle =
                std::make_shared<BallPivotingTriangle>(v0, v1, v2, center);

        BallPivotingEdgePtr e0 = GetLinkingEdge(v0, v1);
        if (e0 == nullptr) {
            e0 = std::make_shared<BallPivotingEdge>(v0, v1);
        }
        e0->AddAdjacentTriangle(triangle);
        v0->edges_.insert(e0);
        v1->edges_.insert(e0);

        BallPivotingEdgePtr e1 = GetLinkingEdge(v1, v2);
        if (e1 == nullptr) {
            e1 = std::make_shared<BallPivotingEdge>(v1, v2);
        }
        e1->AddAdjacentTriangle(triangle);
        v1->edges_.insert(e1);
        v2->edges_.insert(e1);

        BallPivotingEdgePtr e2 = GetLinkingEdge(v2, v0);
        if (e2 == nullptr) {
            e2 = std::make_shared<BallPivotingEdge>(v2, v0);
        }
        e2->AddAdjacentTriangle(triangle);
        v2->edges_.insert(e2);
        v0->edges_.insert(e2);

        v0->UpdateType();
        v1->UpdateType();
        v2->UpdateType();

        Eigen::Vector3d face_normal =
                ComputeFaceNormal(v0->point_, v1->point_, v2->point_);
        if (face_normal.dot(v0->normal_) > -1e-16) {
            mesh_->triangles_.emplace_back(
                    Eigen::Vector3i(v0->idx_, v1->idx_, v2->idx_));
        } else {
            mesh_->triangles_.emplace_back(
                    Eigen::Vector3i(v0->idx_, v2->idx_, v1->idx_));
        }
        mesh_->triangle_normals_.push_back(face_normal);
    }

    Eigen::Vector3d ComputeFaceNormal(const Eigen::Vector3d& v0,
                                      const Eigen::Vector3d& v1,
                                      const Eigen::Vector3d& v2) {
        Eigen::Vector3d normal = (v1 - v0).cross(v2 - v0);
        double norm = normal.norm();
        if (norm > 0) {
            normal /= norm;
        }
        return normal;
    }

    bool IsCompatible(const BallPivotingVertexPtr& v0,
                      const BallPivotingVertexPtr& v1,
                      const BallPivotingVertexPtr& v2) {
        utility::LogDebug("[IsCompatible] v0.idx={}, v1.idx={}, v2.idx={}",
                          v0->idx_, v1->idx_, v2->idx_);
        Eigen::Vector3d normal =
                ComputeFaceNormal(v0->point_, v1->point_, v2->point_);
        if (normal.dot(v0->normal_) < -1e-16) {
            normal *= -1;
        }
        bool ret = normal.dot(v0->normal_) > -1e-16 &&
                   normal.dot(v1->normal_) > -1e-16 &&
                   normal.dot(v2->normal_) > -1e-16;
        utility::LogDebug("[IsCompatible] returns = {}", ret);
        return ret;
    }

    BallPivotingVertexPtr FindCandidateVertex(
            const BallPivotingEdgePtr& edge,
            double radius,
            Eigen::Vector3d& candidate_center) {
        utility::LogDebug("[FindCandidateVertex] edge=({}, {}), radius={}",
                          edge->source_->idx_, edge->target_->idx_, radius);
        BallPivotingVertexPtr src = edge->source_;
        BallPivotingVertexPtr tgt = edge->target_;

        const BallPivotingVertexPtr opp = edge->GetOppositeVertex();
        if (opp == nullptr) {
            utility::LogError("edge->GetOppositeVertex() returns nullptr.");
        }
        utility::LogDebug("[FindCandidateVertex] edge=({}, {}), opp={}",
                          src->idx_, tgt->idx_, opp->idx_);
        utility::LogDebug("[FindCandidateVertex] src={} => {}", src->idx_,
                          src->point_.transpose());
        utility::LogDebug("[FindCandidateVertex] tgt={} => {}", tgt->idx_,
                          tgt->point_.transpose());
        utility::LogDebug("[FindCandidateVertex] src={} => {}", opp->idx_,
                          opp->point_.transpose());

        Eigen::Vector3d mp = 0.5 * (src->point_ + tgt->point_);
        utility::LogDebug("[FindCandidateVertex] edge=({}, {}), mp={}",
                          edge->source_->idx_, edge->target_->idx_,
                          mp.transpose());

        BallPivotingTrianglePtr triangle = edge->triangle0_;
        const Eigen::Vector3d& center = triangle->ball_center_;
        utility::LogDebug("[FindCandidateVertex] edge=({}, {}), center={}",
                          edge->source_->idx_, edge->target_->idx_,
                          center.transpose());

        Eigen::Vector3d v = tgt->point_ - src->point_;
        v /= v.norm();

        Eigen::Vector3d a = center - mp;
        a /= a.norm();

        std::vector<int> indices;
        std::vector<double> dists2;
        kdtree_.SearchRadius(mp, 2 * radius, indices, dists2);
        utility::LogDebug("[FindCandidateVertex] found {} potential candidates",
                          indices.size());

        BallPivotingVertexPtr min_candidate = nullptr;
        double min_angle = 2 * M_PI;
        for (auto nbidx : indices) {
            utility::LogDebug("[FindCandidateVertex] nbidx {:d}", nbidx);
            const BallPivotingVertexPtr& candidate = vertices[nbidx];
            if (candidate->idx_ == src->idx_ || candidate->idx_ == tgt->idx_ ||
                candidate->idx_ == opp->idx_) {
                utility::LogDebug(
                        "[FindCandidateVertex] candidate {:d} is a triangle "
                        "vertex of the edge",
                        candidate->idx_);
                continue;
            }
            utility::LogDebug("[FindCandidateVertex] candidate={:d} => {}",
                              candidate->idx_, candidate->point_.transpose());

            bool coplanar = IntersectionTest::PointsCoplanar(
                    src->point_, tgt->point_, opp->point_, candidate->point_);
            if (coplanar && (IntersectionTest::LineSegmentsMinimumDistance(
                                     mp, candidate->point_, src->point_,
                                     opp->point_) < 1e-12 ||
                             IntersectionTest::LineSegmentsMinimumDistance(
                                     mp, candidate->point_, tgt->point_,
                                     opp->point_) < 1e-12)) {
                utility::LogDebug(
                        "[FindCandidateVertex] candidate {:d} is intersecting "
                        "the existing triangle",
                        candidate->idx_);
                continue;
            }

            Eigen::Vector3d new_center;
            if (!ComputeBallCenter(src->idx_, tgt->idx_, candidate->idx_,
                                   radius, new_center)) {
                utility::LogDebug(
                        "[FindCandidateVertex] candidate {:d} can not compute "
                        "ball",
                        candidate->idx_);
                continue;
            }
            utility::LogDebug("[FindCandidateVertex] candidate {:d} center={}",
                              candidate->idx_, new_center.transpose());

            Eigen::Vector3d b = new_center - mp;
            b /= b.norm();
            utility::LogDebug(
                    "[FindCandidateVertex] candidate {:d} v={}, a={}, b={}",
                    candidate->idx_, v.transpose(), a.transpose(),
                    b.transpose());

            double cosinus = a.dot(b);
            cosinus = std::min(cosinus, 1.0);
            cosinus = std::max(cosinus, -1.0);
            utility::LogDebug(
                    "[FindCandidateVertex] candidate {:d} cosinus={:f}",
                    candidate->idx_, cosinus);

            double angle = std::acos(cosinus);

            Eigen::Vector3d c = a.cross(b);
            if (c.dot(v) < 0) {
                angle = 2 * M_PI - angle;
            }

            if (angle >= min_angle) {
                utility::LogDebug(
                        "[FindCandidateVertex] candidate {:d} angle {:f} > "
                        "min_angle {:f}",
                        candidate->idx_, angle, min_angle);
                continue;
            }

            bool empty_ball = true;
            for (auto nbidx2 : indices) {
                const BallPivotingVertexPtr& nb = vertices[nbidx2];
                if (nb->idx_ == src->idx_ || nb->idx_ == tgt->idx_ ||
                    nb->idx_ == candidate->idx_) {
                    continue;
                }
                if ((new_center - nb->point_).norm() < radius - 1e-16) {
                    utility::LogDebug(
                            "[FindCandidateVertex] candidate {:d} not an empty "
                            "ball",
                            candidate->idx_);
                    empty_ball = false;
                    break;
                }
            }

            if (empty_ball) {
                utility::LogDebug("[FindCandidateVertex] candidate {:d} works",
                                  candidate->idx_);
                min_angle = angle;
                min_candidate = vertices[nbidx];
                candidate_center = new_center;
            }
        }

        if (min_candidate == nullptr) {
            utility::LogDebug("[FindCandidateVertex] returns nullptr");
        } else {
            utility::LogDebug("[FindCandidateVertex] returns {:d}",
                              min_candidate->idx_);
        }
        return min_candidate;
    }

    void ExpandTriangulation(double radius) {
        utility::LogDebug("[ExpandTriangulation] radius={}", radius);
        while (!edge_front_.empty()) {
            BallPivotingEdgePtr edge = edge_front_.front();
            edge_front_.pop_front();
            if (edge->type_ != BallPivotingEdge::Front) {
                continue;
            }

            Eigen::Vector3d center;
            BallPivotingVertexPtr candidate =
                    FindCandidateVertex(edge, radius, center);
            if (candidate == nullptr ||
                candidate->type_ == BallPivotingVertex::Type::Inner ||
                !IsCompatible(candidate, edge->source_, edge->target_)) {
                edge->type_ = BallPivotingEdge::Type::Border;
                border_edges_.push_back(edge);
                continue;
            }

            BallPivotingEdgePtr e0 = GetLinkingEdge(candidate, edge->source_);
            BallPivotingEdgePtr e1 = GetLinkingEdge(candidate, edge->target_);
            if ((e0 != nullptr && e0->type_ != BallPivotingEdge::Type::Front) ||
                (e1 != nullptr && e1->type_ != BallPivotingEdge::Type::Front)) {
                edge->type_ = BallPivotingEdge::Type::Border;
                border_edges_.push_back(edge);
                continue;
            }

            CreateTriangle(edge->source_, edge->target_, candidate, center);

            e0 = GetLinkingEdge(candidate, edge->source_);
            e1 = GetLinkingEdge(candidate, edge->target_);
            if (e0->type_ == BallPivotingEdge::Type::Front) {
                edge_front_.push_front(e0);
            }
            if (e1->type_ == BallPivotingEdge::Type::Front) {
                edge_front_.push_front(e1);
            }
        }
    }

    bool TryTriangleSeed(const BallPivotingVertexPtr& v0,
                         const BallPivotingVertexPtr& v1,
                         const BallPivotingVertexPtr& v2,
                         const std::vector<int>& nb_indices,
                         double radius,
                         Eigen::Vector3d& center) {
        utility::LogDebug(
                "[TryTriangleSeed] v0.idx={}, v1.idx={}, v2.idx={}, "
                "radius={}",
                v0->idx_, v1->idx_, v2->idx_, radius);

        if (!IsCompatible(v0, v1, v2)) {
            return false;
        }

        BallPivotingEdgePtr e0 = GetLinkingEdge(v0, v2);
        BallPivotingEdgePtr e1 = GetLinkingEdge(v1, v2);
        if (e0 != nullptr && e0->type_ == BallPivotingEdge::Type::Inner) {
            utility::LogDebug(
                    "[TryTriangleSeed] returns {} because e0 is inner edge",
                    false);
            return false;
        }
        if (e1 != nullptr && e1->type_ == BallPivotingEdge::Type::Inner) {
            utility::LogDebug(
                    "[TryTriangleSeed] returns {} because e1 is inner edge",
                    false);
            return false;
        }

        if (!ComputeBallCenter(v0->idx_, v1->idx_, v2->idx_, radius, center)) {
            utility::LogDebug(
                    "[TryTriangleSeed] returns {} could not compute ball "
                    "center",
                    false);
            return false;
        }

        // test if no other point is within the ball
        for (const auto& nbidx : nb_indices) {
            const BallPivotingVertexPtr& v = vertices[nbidx];
            if (v->idx_ == v0->idx_ || v->idx_ == v1->idx_ ||
                v->idx_ == v2->idx_) {
                continue;
            }
            if ((center - v->point_).norm() < radius - 1e-16) {
                utility::LogDebug(
                        "[TryTriangleSeed] returns {} computed ball is not "
                        "empty",
                        false);
                return false;
            }
        }

        utility::LogDebug("[TryTriangleSeed] returns {}", true);
        return true;
    }

    bool TrySeed(BallPivotingVertexPtr& v, double radius) {
        utility::LogDebug("[TrySeed] with v.idx={}, radius={}", v->idx_,
                          radius);
        std::vector<int> indices;
        std::vector<double> dists2;
        kdtree_.SearchRadius(v->point_, 2 * radius, indices, dists2);
        if (indices.size() < 3u) {
            return false;
        }

        for (size_t nbidx0 = 0; nbidx0 < indices.size(); ++nbidx0) {
            const BallPivotingVertexPtr& nb0 = vertices[indices[nbidx0]];
            if (nb0->type_ != BallPivotingVertex::Type::Orphan) {
                continue;
            }
            if (nb0->idx_ == v->idx_) {
                continue;
            }

            int candidate_vidx2 = -1;
            Eigen::Vector3d center;
            for (size_t nbidx1 = nbidx0 + 1; nbidx1 < indices.size();
                 ++nbidx1) {
                const BallPivotingVertexPtr& nb1 = vertices[indices[nbidx1]];
                if (nb1->type_ != BallPivotingVertex::Type::Orphan) {
                    continue;
                }
                if (nb1->idx_ == v->idx_) {
                    continue;
                }
                if (TryTriangleSeed(v, nb0, nb1, indices, radius, center)) {
                    candidate_vidx2 = nb1->idx_;
                    break;
                }
            }

            if (candidate_vidx2 >= 0) {
                const BallPivotingVertexPtr& nb1 = vertices[candidate_vidx2];

                BallPivotingEdgePtr e0 = GetLinkingEdge(v, nb1);
                if (e0 != nullptr &&
                    e0->type_ != BallPivotingEdge::Type::Front) {
                    continue;
                }
                BallPivotingEdgePtr e1 = GetLinkingEdge(nb0, nb1);
                if (e1 != nullptr &&
                    e1->type_ != BallPivotingEdge::Type::Front) {
                    continue;
                }
                BallPivotingEdgePtr e2 = GetLinkingEdge(v, nb0);
                if (e2 != nullptr &&
                    e2->type_ != BallPivotingEdge::Type::Front) {
                    continue;
                }

                CreateTriangle(v, nb0, nb1, center);

                e0 = GetLinkingEdge(v, nb1);
                e1 = GetLinkingEdge(nb0, nb1);
                e2 = GetLinkingEdge(v, nb0);
                if (e0->type_ == BallPivotingEdge::Type::Front) {
                    edge_front_.push_front(e0);
                }
                if (e1->type_ == BallPivotingEdge::Type::Front) {
                    edge_front_.push_front(e1);
                }
                if (e2->type_ == BallPivotingEdge::Type::Front) {
                    edge_front_.push_front(e2);
                }

                if (edge_front_.size() > 0) {
                    utility::LogDebug(
                            "[TrySeed] edge_front_.size() > 0 => return "
                            "true");
                    return true;
                }
            }
        }

        utility::LogDebug("[TrySeed] return false");
        return false;
    }

    void FindSeedTriangle(double radius) {
        for (size_t vidx = 0; vidx < vertices.size(); ++vidx) {
            utility::LogDebug("[FindSeedTriangle] with radius={}, vidx={}",
                              radius, vidx);
            if (vertices[vidx]->type_ == BallPivotingVertex::Type::Orphan) {
                if (TrySeed(vertices[vidx], radius)) {
                    ExpandTriangulation(radius);
                }
            }
        }
    }

    std::shared_ptr<TriangleMesh> Run(const std::vector<double>& radii) {
        if (!has_normals_) {
            utility::LogError("ReconstructBallPivoting requires normals");
        }

        mesh_->triangles_.clear();

        for (double radius : radii) {
            utility::LogDebug("[Run] ################################");
            utility::LogDebug("[Run] change to radius {:.4f}", radius);
            if (radius <= 0) {
                utility::LogError(
                        "got an invalid, negative radius as parameter");
            }

            // update radius => update border edges
            for (auto it = border_edges_.begin(); it != border_edges_.end();) {
                BallPivotingEdgePtr edge = *it;
                BallPivotingTrianglePtr triangle = edge->triangle0_;
                utility::LogDebug(
                        "[Run] try edge {:d}-{:d} of triangle {:d}-{:d}-{:d}",
                        edge->source_->idx_, edge->target_->idx_,
                        triangle->vert0_->idx_, triangle->vert1_->idx_,
                        triangle->vert2_->idx_);

                Eigen::Vector3d center;
                if (ComputeBallCenter(triangle->vert0_->idx_,
                                      triangle->vert1_->idx_,
                                      triangle->vert2_->idx_, radius, center)) {
                    utility::LogDebug("[Run]   yes, we can work on this");
                    std::vector<int> indices;
                    std::vector<double> dists2;
                    kdtree_.SearchRadius(center, radius, indices, dists2);
                    bool empty_ball = true;
                    for (auto idx : indices) {
                        if (idx != triangle->vert0_->idx_ &&
                            idx != triangle->vert1_->idx_ &&
                            idx != triangle->vert2_->idx_) {
                            utility::LogDebug(
                                    "[Run]   but no, the ball is not empty");
                            empty_ball = false;
                            break;
                        }
                    }

                    if (empty_ball) {
                        utility::LogDebug(
                                "[Run]   yeah, add edge to edge_front_: {:d}",
                                edge_front_.size());
                        edge->type_ = BallPivotingEdge::Type::Front;
                        edge_front_.push_back(edge);
                        it = border_edges_.erase(it);
                        continue;
                    }
                }
                ++it;
            }

            // do the reconstruction
            if (edge_front_.empty()) {
                FindSeedTriangle(radius);
            } else {
                ExpandTriangulation(radius);
            }

            utility::LogDebug("[Run] mesh_ has {:d} triangles",
                              mesh_->triangles_.size());
            utility::LogDebug("[Run] ################################");
        }
        return mesh_;
    }

private:
    bool has_normals_;
    KDTreeFlann kdtree_;
    std::list<BallPivotingEdgePtr> edge_front_;
    std::list<BallPivotingEdgePtr> border_edges_;
    std::vector<BallPivotingVertexPtr> vertices;
    std::shared_ptr<TriangleMesh> mesh_;
};

std::shared_ptr<TriangleMesh> TriangleMesh::CreateFromPointCloudBallPivoting(
        const PointCloud& pcd, const std::vector<double>& radii) {
    BallPivoting bp(pcd);
    return bp.Run(radii);
}

}  // namespace geometry
}  // namespace open3d