harmonics.cpp serif/geometry/deformation/detail/harmonics.hpp algorithm cmath cstdint limits numbers serif serif::geometry serif::geometry::deformation serif::geometry::deformation::detail #include"serif/geometry/deformation/detail/harmonics.hpp" #include<algorithm> #include<cmath> #include<cstdint> #include<limits> #include<numbers> namespaceserif::geometry::deformation::detail{ namespace{ [[nodiscard]]doublecolumn_product(std::span<constdouble>basis,std::size_trows,std::size_tcolumns,std::size_tfirst,std::size_tsecond){ longdoubleresult=0.0L; for(std::size_trow=0;row<rows;++row){ result+=static_cast<longdouble>(basis[row*columns+first])*basis[row*columns+second]; } returnstatic_cast<double>(result); } voidevaluate_legendre(conststd::array<double,3>&direction,intmaximum_degree,intmaximum_order,std::span<double>legendre){ conststd::size_tstride=static_cast<std::size_t>(maximum_order)+1; constdoublecylindrical_radius=std::hypot(direction[0],direction[1]); constautopolynomial=[&](intdegree,intorder)->double&{ returnlegendre[static_cast<std::size_t>(degree)*stride+static_cast<std::size_t>(order)]; }; polynomial(0,0)=1.0/std::sqrt(4.0*std::numbers::pi); for(intorder=0;order<=maximum_order;++order){ if(order>0){ polynomial(order,order)=-std::sqrt((2.0*order+1.0)/(2.0*order))*cylindrical_radius*polynomial(order-1,order-1); } if(order<maximum_degree){ polynomial(order+1,order)=std::sqrt(2.0*order+3.0)*direction[2]*polynomial(order,order); } for(intdegree=order+2;degree<=maximum_degree;++degree){ constdoubledegree_value=degree; constdoubleorder_value=order; constdoubleleading=std::sqrt((4.0*degree_value*degree_value-1.0)/(degree_value*degree_value-order_value*order_value)); constdoubletrailing=std::sqrt(((2.0*degree_value+1.0)*((degree_value-1.0)*(degree_value-1.0)-order_value*order_value))/((2.0*degree_value-3.0)*(degree_value*degree_value-order_value*order_value))); polynomial(degree,order)=leading*direction[2]*polynomial(degree-1,order)-trailing*polynomial(degree-2,order); } } } } std::expected<std::vector<SphericalHarmonicMode>,DeformationFailure>make_harmonic_modes(constSphericalHarmonicSurface&model,std::size_tnode_count){ constintmaximum_order=model.maximum_azimuthal_order.value_or(model.maximum_degree); if(model.maximum_degree<0||maximum_order<0||maximum_order>model.maximum_degree){ returnstd::unexpected(DeformationFailure{.reason=DeformationFailureReason::invalid_configuration,.message="Themaximumharmonicdegreemustbenonnegativeandtheazimuthalcutoffmustliebetweenzeroandthatdegree."}); } conststd::int64_tdegree=model.maximum_degree; conststd::int64_torder=maximum_order; conststd::int64_tcount=(order+1)*(order+1)+(degree-order)*(2*order+1); if(count>std::numeric_limits<int>::max()){ returnstd::unexpected(DeformationFailure{.reason=DeformationFailureReason::invalid_configuration,.message="Therequestedharmonicmodecountexceedssupportedvectorindexing."}); } if(static_cast<std::size_t>(count)>node_count){ returnstd::unexpected(DeformationFailure{.reason=DeformationFailureReason::unresolved_surface,.message="Therequestedharmonicmodecountexceedsthenumberofsurfacenodes;reducethedegreeorrefinethesurface."}); } std::vector<SphericalHarmonicMode>modes; modes.reserve(static_cast<std::size_t>(count)); for(intdegree_index=0;degree_index<=model.maximum_degree;++degree_index){ constintcutoff=std::min(degree_index,maximum_order); for(intorder_index=-cutoff;order_index<=cutoff;++order_index){ modes.push_back(SphericalHarmonicMode{.degree=degree_index,.order=order_index}); } } returnmodes; } std::expected<std::vector<double>,DeformationFailure>sample_harmonic_basis(constSphericalHarmonicSurface&model,std::span<constSphericalHarmonicMode>modes,std::span<conststd::array<double,3>>directions){ if(modes.empty()||directions.size()>std::vector<double>{}.max_size()/modes.size()){ returnstd::unexpected(DeformationFailure{.reason=DeformationFailureReason::invalid_size,.message="Therequestedsampledharmonicbasisexceedssupportedstorageindexing."}); } constintmaximum_order=model.maximum_azimuthal_order.value_or(model.maximum_degree); conststd::size_tstride=static_cast<std::size_t>(maximum_order)+1; std::vector<double>legendre((static_cast<std::size_t>(model.maximum_degree)+1)*stride,0.0); std::vector<double>basis(directions.size()*modes.size()); for(std::size_tnode=0;node<directions.size();++node){ evaluate_legendre(directions[node],model.maximum_degree,maximum_order,legendre); constdoubleazimuth=std::atan2(directions[node][1],directions[node][0]); for(std::size_tcolumn=0;column<modes.size();++column){ constSphericalHarmonicModemode=modes[column]; doublevalue=legendre[static_cast<std::size_t>(mode.degree)*stride+static_cast<std::size_t>(std::abs(mode.order))]; if(mode.order<0){ value*=std::sqrt(2.0)*std::sin(-mode.order*azimuth); } if(mode.order>0){ value*=std::sqrt(2.0)*std::cos(mode.order*azimuth); } if(!std::isfinite(value)){ returnstd::unexpected(DeformationFailure{.reason=DeformationFailureReason::non_finite,.message="Samplingthesphericalharmonicbasisproducedanon-finitevalue."}); } basis[node*modes.size()+column]=value; } } returnbasis; } DeformationStatuscheck_independent_harmonics(std::span<constdouble>basis,std::size_tnode_count,std::size_tmode_count){ std::vector<double>orthogonal(basis.begin(),basis.end()); constexprdoubleminimum_relative_pivot=1e-12; for(std::size_tcolumn=0;column<mode_count;++column){ constdoubleoriginal=std::sqrt(column_product(basis,node_count,mode_count,column,column)); for(intpass=0;pass<2;++pass){ for(std::size_tprevious=0;previous<column;++previous){ constdoubleprojection=column_product(orthogonal,node_count,mode_count,previous,column); for(std::size_trow=0;row<node_count;++row){ orthogonal[row*mode_count+column]-=projection*orthogonal[row*mode_count+previous]; } } } constdoublepivot=std::sqrt(column_product(orthogonal,node_count,mode_count,column,column)); if(!std::isfinite(original)||!(original>0.0)||!std::isfinite(pivot)||!(pivot>minimum_relative_pivot*original)){ returnstd::unexpected(DeformationFailure{.reason=DeformationFailureReason::unresolved_surface,.message="Theselectedharmonicsarenumericallydependentonthesurfacenodes;reducetheirdegreeororder,orrefinethesurface."}); } for(std::size_trow=0;row<node_count;++row){ orthogonal[row*mode_count+column]/=pivot; } } return{}; } }