Program Listing for File SurfaceTension_ZorillaRitter2020.cpp
↰ Return to documentation for file (SPlisHSPlasH/SurfaceTension/SurfaceTension_ZorillaRitter2020.cpp)
/*
MIT License
Copyright (c) 2020 Fernando Zorilla and Marcel Ritter
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.
*/
/*
Find supplementary material here:
https://github.com/gileoo/MCSurfaceTension2020-Supplemental
Please get in contact for feedback/support.
*/
#include <iostream>
#include <fstream>
#include <map>
#include "SPlisHSPlasH/Simulation.h"
#include "SPlisHSPlasH/TimeManager.h"
#include "SPlisHSPlasH/Utilities/MathFunctions.h"
#include "Utilities/StringTools.h"
#include "SurfaceTension_ZorillaRitter2020_haltonVec323.h"
#include "SurfaceTension_ZorillaRitter2020.h"
using namespace SPH;
using namespace std;
using namespace chrono;
using namespace GenParam;
std::string SurfaceTension_ZorillaRitter2020::METHOD_NAME = "Zorilla, Ritter, et al. 2020";
// -- GUI variable handles
int SurfaceTension_ZorillaRitter2020::SURFACE_TENSION = -1;
int SurfaceTension_ZorillaRitter2020::SURFACE_TENSION_MEDIAN = -1;
int SurfaceTension_ZorillaRitter2020::VERSION = -1;
int SurfaceTension_ZorillaRitter2020::ENUM_VERSION_V2019 = -1;
int SurfaceTension_ZorillaRitter2020:: ENUM_VERSION_V2020 = -1;
int SurfaceTension_ZorillaRitter2020::CSD = -1;
int SurfaceTension_ZorillaRitter2020::R2MULT = -1;
int SurfaceTension_ZorillaRitter2020::TAU = -1;
int SurfaceTension_ZorillaRitter2020::PCA_NRM_MODE = -1;
int SurfaceTension_ZorillaRitter2020::CLASS_D = -1;
int SurfaceTension_ZorillaRitter2020::CLASS_D_OFF = -1;
int SurfaceTension_ZorillaRitter2020::PCA_NRM_MIX = -1;
int SurfaceTension_ZorillaRitter2020::PCA_CUR_MIX = -1;
int SurfaceTension_ZorillaRitter2020::FIX_SAMPLES = -1;
int SurfaceTension_ZorillaRitter2020::NEIGH_LIMIT = -1;
int SurfaceTension_ZorillaRitter2020::SAMPLING = -1;
int SurfaceTension_ZorillaRitter2020::SAMPLING_HALTON = -1;
int SurfaceTension_ZorillaRitter2020::SAMPLING_RND = -1;
int SurfaceTension_ZorillaRitter2020::NORMAL_MODE = -1;
int SurfaceTension_ZorillaRitter2020::NORMAL_PCA = -1;
int SurfaceTension_ZorillaRitter2020::NORMAL_MC = -1;
int SurfaceTension_ZorillaRitter2020::NORMAL_MIX = -1;
int SurfaceTension_ZorillaRitter2020::SMOOTH_PASSES = -1;
int SurfaceTension_ZorillaRitter2020::TEMPORAL_SMOOTH = -1;
void SPH::SurfaceTension_ZorillaRitter2020::setupGUIEnum(int& PARAMID, string name, string group, string description,
std::vector<pair<string, int*>> enum_ids,
const GenParam::ParameterBase::GetFunc<int>& getter,
const GenParam::ParameterBase::SetFunc<int>& setter)
{
vector<string> tmp;
Utilities::StringTools::tokenize(name, tmp, " ");
PARAMID = createEnumParameter("surfTZR" + tmp[0], name, getter, setter);
setGroup(PARAMID, group);
setDescription(PARAMID, description);
GenParam::EnumParameter* enumParam = static_cast<GenParam::EnumParameter*>(getParameter(PARAMID));
for (auto& s : enum_ids)
enumParam->addEnumValue(s.first, *s.second);
}
void SurfaceTension_ZorillaRitter2020::resizeV19States( size_t N )
{
m_mc_normals.resize( N, Vector3r::Zero() );
m_final_curvatures.resize( N, 0.0 );
}
void SurfaceTension_ZorillaRitter2020::resizeV20States( size_t N )
{
m_pca_curv.resize( N, 0.0 );
m_pca_curv_smooth.resize( N, 0.0 );
m_mc_curv.resize(N, 0.0);
m_mc_curv_smooth.resize( N, 0.0 );
m_mc_normals_smooth.resize( N, Vector3r::Zero() );
m_pca_normals.resize( N, Vector3r::Zero() );
m_final_curvatures_old.resize( N, 0.0 );
m_classifier_input.resize( N, 0.0 );
m_classifier_output.resize(N, 0.0);
}
SurfaceTension_ZorillaRitter2020::SurfaceTension_ZorillaRitter2020(FluidModel* model)
: NonPressureForceBase(model)
, m_surfaceTension(0.05)
, m_step_version(StepVersion::V2020)
, m_Csd(10000) // 10000 // 36000 // 48000 // 60000
, m_tau(0.5)
, m_r2mult(0.8)
, m_r1(Simulation::getCurrent()->getSupportRadius())
, m_r2(m_r2mult* m_r1)
, m_class_k( 74.688796680497925 )
, m_class_d( 12 )
, m_temporal_smoothing( false )
, m_CsdFix( -1 )
, m_class_d_off( 2 )
, m_pca_N_mix( 0.75 )
, m_pca_C_mix( 0.5 )
, m_neighs_limit( 16 )
, m_CS_smooth_passes( 1 )
, m_halton_sampling(RandomMethod::HALTON)
, m_normal_mode( NormalMethod::MC )
{
m_model->addField( { "Curv.Final (SurfZR20)", METHOD_NAME, FieldType::Scalar, [this](const unsigned int i ) -> Real* { return &this->getFinalCurvature( i ); } } );
m_model->addField( { "Surf.Class (SurfZR20)", METHOD_NAME, FieldType::Scalar, [this](const unsigned int i) -> Real* { return &this->getClassifierOutput( i ); } });
// -- Both Versions
resizeV19States( model->numParticles() );
// -- V2020 only
resizeV20States( model->numParticles() );
// initParameters();
}
// -- setup GUI control parameters
void SurfaceTension_ZorillaRitter2020::initParameters()
{
using enumGet = GenParam::ParameterBase::GetFunc<int>;
using enumSet = GenParam::ParameterBase::SetFunc<int>;
NonPressureForceBase::initParameters();
SURFACE_TENSION = createNumericParameter("surfaceTension", "Surface tension coefficient", &m_surfaceTension);
setGroup(SURFACE_TENSION, "Fluid Model|Surface tension");
setDescription(SURFACE_TENSION, "Coefficient for the surface tension computation");
RealParameter* rparam = static_cast<RealParameter*>(getParameter(SURFACE_TENSION));
rparam->setMinValue(0.0);
const string grp19 = "Fluid Model|Surface tension"; // "Surface tension ZR19";
const string grp20 = "Fluid Model|Surface tension"; // "Surface tension ZR20";
enumGet getVersionFct = std::bind( &SurfaceTension_ZorillaRitter2020::getVersionMethod, this );
enumSet setVersionFct = std::bind( &SurfaceTension_ZorillaRitter2020::setVersionMethod, this, std::placeholders::_1 );
setupGUIEnum(VERSION, "version", "Fluid Model|Surface tension", "Method or extended method.",
{ { "V2019", &ENUM_VERSION_V2019 }, { "V2020", &ENUM_VERSION_V2020 } },
getVersionFct, setVersionFct );
// 2019 & 2020
setupGUIParam<int> (CSD, "Csd (19)", grp19, "Samples per Second.", &m_Csd);
setupGUIParam<Real>(R2MULT, "r-ratio (19)", grp19, "R1 to R2 ratio.", &m_r2mult);
setupGUIParam<Real>(TAU, "tau (19)", grp19, "Smoothing factor tau.", &m_tau);
setupGUIParam<Real>(CLASS_D, "d (19)", grp19, "Classifier constant d.", &m_class_d);
// 2020
enumGet getSamplingFct = std::bind(&SurfaceTension_ZorillaRitter2020::getSamplingMethod, this);
enumSet setSamplingFct = std::bind(&SurfaceTension_ZorillaRitter2020::setSamplingMethod, this, std::placeholders::_1);
setupGUIEnum(SAMPLING, "sampling (20)", grp20, "Monte Carlo samping method.",
{ { "Halton", &SAMPLING_HALTON }, { "Rnd", &SAMPLING_RND } },
getSamplingFct, setSamplingFct);
enumGet getNormalFct = std::bind(&SurfaceTension_ZorillaRitter2020::getNormalMethod, this);
enumSet setNormalFct = std::bind(&SurfaceTension_ZorillaRitter2020::setNormalMethod, this, std::placeholders::_1);
setupGUIEnum(NORMAL_MODE, "normal-mode (20)", grp20, "Normal estimation method.",
{ { "PCA", &NORMAL_PCA}, { "Monte Carlo", &NORMAL_MC}, { "Mix", &NORMAL_MIX} },
getNormalFct, setNormalFct);
setupGUIParam<int> (FIX_SAMPLES, "MCSamples (20)", grp20, "Fixed nr of MC samples per step.", &m_CsdFix);
setupGUIParam<Real>(PCA_CUR_MIX, "PcaMixCur (20)", grp20, "Factor to mix-in pca curvature.", &m_pca_C_mix);
setupGUIParam<Real>(PCA_NRM_MIX, "PcaMixNrm (20)", grp20, "Factor to mix-in pca normal.", &m_pca_N_mix);
TEMPORAL_SMOOTH = createBoolParameter("surfTZRtemporalSmooth" , "temporalSmoothing (20)", &m_temporal_smoothing);
setGroup(TEMPORAL_SMOOTH, grp20);
setDescription(TEMPORAL_SMOOTH, "Enable temporal smoothing");
}
SurfaceTension_ZorillaRitter2020::~SurfaceTension_ZorillaRitter2020( void )
{
Simulation* sim = Simulation::getCurrent();
m_model->removeFieldByName( "Curv.Final (SurfZR20)" );
m_model->removeFieldByName( "Surf.Class (SurfZR20)" );
}
bool SurfaceTension_ZorillaRitter2020::evaluateNetwork( double com, int non )
{
double W111 = 0.94356692; double W112 = -38.35266876;
double W121 = -0.95363748; double W122 = 0.50992483;
double B11 = 0; double B12 = -4.76320028;
double W211 = -1.10649812; double W212 = -0.43858105;
double W221 = 0.92051727; double W222 = -0.99366635;
double B21 = -8.12260151; double B22 = 0.95201129;
double L11in = com * W111 + non * W121 + B11;
double L12in = com * W112 + non * W122 + B12;
double L11out = std::max( 0.0, L11in );
double L12out = std::max( 0.0, L12in );
double L21in = L11out * W211 + L12out * W221 + B21;
double L22in = L11out * W212 + L12out * W222 + B22;
double L21out = 1 / (1 + exp( -L21in ));
double L22out = 1 / (1 + exp( -L22in ));
if (L21out < L22out) { return true; }
else { return false; }
}
bool SurfaceTension_ZorillaRitter2020::classifySurfaceParticle( double com, int non, double d_offset)
{
double neighborsOnTheLine = 74.688796680497925 * com + 28.0; // pre-multiplied
if (non <= neighborsOnTheLine)
return true;
else
return false;
}
bool SurfaceTension_ZorillaRitter2020::classifyParticleConfigurable( double com, int non, double d_offset )
{
double neighborsOnTheLine = m_class_k * com + m_class_d + d_offset; // pre-multiplied
if (non <= neighborsOnTheLine)
return true;
else
return false;
}
void SurfaceTension_ZorillaRitter2020::performNeighborhoodSearchSort()
{
Simulation* sim = Simulation::getCurrent();
const unsigned int nModels = sim->numberOfFluidModels();
const unsigned int fluidModelIndex = m_model->getPointSetIndex();
auto const& d = sim->getNeighborhoodSearch()->point_set(fluidModelIndex);
// Both version state fields
d.sort_field(&m_mc_normals[0]);
d.sort_field(&m_final_curvatures[0]);
// Both version 2020 state fields
if (m_step_version == StepVersion::V2020)
{
d.sort_field(&m_pca_curv[0]);
d.sort_field(&m_pca_curv_smooth[0]);
d.sort_field(&m_mc_curv[0]);
d.sort_field(&m_mc_curv_smooth[0]);
d.sort_field(&m_mc_normals_smooth[0]);
d.sort_field(&m_pca_normals[0]);
d.sort_field(&m_final_curvatures_old[0]);
d.sort_field(&m_classifier_input[0]);
d.sort_field(&m_classifier_output[0]);
}
}
void SurfaceTension_ZorillaRitter2020::step()
{
// -- switch version
if( m_step_version == StepVersion::V2019 )
stepZorilla();
else
stepRitter();
}
Vector3r SurfaceTension_ZorillaRitter2020::anglesToVec( Real theta, Real phi, Real radius )
{
const Real r_sin_phi = radius * sin( phi );
return Vector3r(
r_sin_phi * cos( theta ),
r_sin_phi * sin( theta ),
radius * cos( phi ) );
}
std::vector<Vector3r> SurfaceTension_ZorillaRitter2020::getSphereSamplesRnd( int N, Real supportRadius )
{
std::vector<Vector3r> points(N);
for (int i = 0; i < N; i++)
{
Real theta = static_cast<Real>(2.0 * M_PI) * static_cast<Real>(rand()) / static_cast<Real>( RAND_MAX );
Real phi = acos(static_cast<Real>(1.0) - static_cast<Real>(2.0) * static_cast<Real>(rand()) / static_cast<Real>( RAND_MAX ) );
points[i] = anglesToVec( theta, phi, supportRadius );
}
return points;
}
void SurfaceTension_ZorillaRitter2020::stepZorilla()
{
Simulation* sim = Simulation::getCurrent();
const Real supportRadius = sim->getSupportRadius();
const unsigned int numParticles = m_model->numActiveParticles();
const Real k = m_surfaceTension;
Real timeStep = SPH::TimeManager::getCurrent()->getTimeStepSize();
Real r1 = supportRadius;
Real r2 = m_r2mult * r1; // m_r2mult=0.8 best results empirically
const unsigned int NumberOfPoints = int( m_Csd * timeStep );
const unsigned int fluidModelIndex = m_model->getPointSetIndex();
FluidModel* model = m_model;
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
m_mc_normals[i] = Vector3r::Zero();
m_classifier_output[i] = 0;
Vector3r centerofMasses = Vector3r::Zero();
int numberOfNeighbours = sim->numberOfNeighbors(fluidModelIndex, fluidModelIndex, i );
if (numberOfNeighbours == 0)
continue;
const Vector3r& xi = m_model->getPosition( i );
forall_fluid_neighbors_in_same_phase(
Vector3r xjxi = (xj - xi);
centerofMasses += xjxi / supportRadius; )
if (classifyParticleConfigurable( centerofMasses.norm() / double( numberOfNeighbours ), numberOfNeighbours )) //EvaluateNetwork also possible
{
std::vector<Vector3r> points = getSphereSamplesRnd( NumberOfPoints, supportRadius );
forall_fluid_neighbors_in_same_phase(
Vector3r xjxi = (xj - xi);
for (int p = static_cast<int>(points.size()) - 1; p >= 0; --p)
{
Vector3r vec = (points[p] - xjxi);
Real dist = vec.squaredNorm();
if (dist <= pow((r2 / r1), 2) * supportRadius * supportRadius)
{
points.erase(points.begin() + p);
}
} )
for (int p = static_cast<int>(points.size()) - 1; p >= 0; --p)
{
m_mc_normals[i] += points[p];
}
if (points.size() > 0)
{
m_mc_normals[i].normalize();
m_mc_curv[i] = (static_cast<Real>(1.0) / supportRadius) * static_cast<Real>(-2.0) * pow( (static_cast<Real>(1.0) - (r2 * r2 / (r1 * r1))), static_cast<Real>(-0.5) ) *
cos( acos(static_cast<Real>(1.0 - 2.0 * (points.size() / static_cast<Real>( NumberOfPoints ))) ) + asin( r2 / r1 ) );
m_classifier_output[i] = 1;
}
else
{
m_mc_curv[i] = 0.0;
m_mc_normals[i] = Vector3r::Zero();
m_classifier_output[i] = 0.5;
}
}
}
}
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
if (m_mc_normals[i] != Vector3r::Zero())
{
const Vector3r& xi = m_model->getPosition(i);
Vector3r normalCorrection = Vector3r::Zero();
Vector3r& ai = m_model->getAcceleration(i);
Real curvatureCorrection = 0;
Real correctionFactor = 0.0;
forall_fluid_neighbors_in_same_phase(
if (m_mc_normals[neighborIndex] != Vector3r::Zero())
{
Vector3r xjxi = (xj - xi);
Real distanceji = xjxi.norm();
normalCorrection += m_mc_normals[neighborIndex] * (1 - distanceji / supportRadius);
curvatureCorrection += m_mc_curv[neighborIndex] * (1 - distanceji / supportRadius);
correctionFactor += (1 - distanceji / supportRadius);
})
normalCorrection.normalize();
normalCorrection = (1 - m_tau) * m_mc_normals[i] + m_tau * normalCorrection;
normalCorrection.normalize();
m_final_curvatures[i] =
((static_cast<Real>(1.0) - m_tau) * m_mc_curv[i] + m_tau * curvatureCorrection)
/ ((static_cast<Real>(1.0) - m_tau) + m_tau * correctionFactor);
ai -= (static_cast<Real>(1.0) / m_model->getMass(i)) * normalCorrection * k * m_final_curvatures[i];
}
else
m_final_curvatures[i] = 0.0;
}
}
}
// -- Helper functions for extended step function
vector<Vector3r> SurfaceTension_ZorillaRitter2020::getSphereSamplesLookUp(
int N, Real supportRadius, int start,
const vector<float>& vec3, int mod )
{
vector<Vector3r> points( N );
int s = (start / 3) * 3; // ensure to be dividable by 3
for (int i = 0; i < N; i++)
{
int i3 = s + 3 * i;
points[i] = supportRadius * Vector3r( vec3[i3 % mod], vec3[(i3 + 1) % mod], vec3[(i3 + 2) % mod] );
}
return points;
}
void SurfaceTension_ZorillaRitter2020::stepRitter()
{
auto& tm = *SPH::TimeManager::getCurrent();
Real timeStep = tm.getTimeStepSize();
size_t step = static_cast<size_t>(floor( tm.getTime() / timeStep ));
m_r2 = m_r1 * m_r2mult;
Simulation* sim = Simulation::getCurrent();
const Real supportRadius = sim->getSupportRadius();
const unsigned int numParticles = m_model->numActiveParticles();
const Real k = m_surfaceTension;
unsigned int NrOfSamples;
const unsigned int fluidModelIndex = m_model->getPointSetIndex();
FluidModel* model = m_model;
if (m_CsdFix > 0)
NrOfSamples = m_CsdFix;
else
NrOfSamples = int( m_Csd * timeStep );
// ################################################################################################
// ## first pass, compute classification and first estimation for normal and curvature (Montecarlo)
// ################################################################################################
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
// init or reset arrays
m_mc_normals[i] = Vector3r::Zero();
m_pca_normals[i] = Vector3r::Zero();
m_mc_normals_smooth[i] = Vector3r::Zero();
m_mc_curv[i] = 0.0;
m_mc_curv_smooth[i] = 0.0;
m_pca_curv[i] = 0.0;
m_pca_curv_smooth[i] = 0.0;
m_final_curvatures[i] = 0.0;
// -- compute center of mass of current particle
Vector3r centerofMasses = Vector3r::Zero();
int numberOfNeighbours = sim->numberOfNeighbors(fluidModelIndex, fluidModelIndex, i );
if (numberOfNeighbours == 0)
{
m_mc_curv[i] = static_cast<Real>(1.0) / supportRadius;
continue;
}
const Vector3r& xi = m_model->getPosition( i );
forall_fluid_neighbors_in_same_phase(
Vector3r xjxi = (xj - xi);
centerofMasses += xjxi; )
centerofMasses /= supportRadius;
// cache classifier input, could also be recomputed later to avoid caching
m_classifier_input[i] = centerofMasses.norm() / static_cast<Real>( numberOfNeighbours );
// -- if it is a surface classified particle
if (classifyParticleConfigurable( m_classifier_input[i], numberOfNeighbours )) //EvaluateNetwork also possible
{
// -- create monte carlo samples on particle
std::vector<Vector3r> points;
if( m_halton_sampling == RandomMethod::HALTON )
points = getSphereSamplesLookUp(
NrOfSamples, supportRadius, i * NrOfSamples, haltonVec323, static_cast<int>(haltonVec323.size()) ); // 8.5 // 15.0(double) // 9.0(float)
else
points = getSphereSamplesRnd( NrOfSamples, supportRadius );
// -- remove samples covered by neighbor spheres
forall_fluid_neighbors_in_same_phase(
Vector3r xjxi = (xj - xi);
for (int p = static_cast<int>(points.size()) - 1; p >= 0; --p)
{
Vector3r vec = (points[p] - xjxi);
Real dist = vec.squaredNorm();
if (dist <= pow( (m_r2 / m_r1), 2 ) * supportRadius * supportRadius)
points.erase( points.begin() + p );
} )
// -- estimate normal by left over sample directions
for (int p = static_cast<int>(points.size()) - 1; p >= 0; --p)
m_mc_normals[i] += points[p];
// -- if surface classified and non-overlapping neighborhood spheres
if (points.size() > 0)
{
m_mc_normals[i].normalize();
// -- estimate curvature by sample ratio and particle radii
m_mc_curv[i] = (static_cast<Real>(1.0) / supportRadius) * static_cast<Real>(-2.0) * pow( (static_cast<Real>(1.0) - (m_r2 * m_r2 / (m_r1 * m_r1))), static_cast<Real>(-0.5) ) *
cos( acos(static_cast<Real>(1.0) - static_cast<Real>(2.0) * (static_cast<Real>( points.size() ) / static_cast<Real>( NrOfSamples )) ) + asin( m_r2 / m_r1 ) );
m_classifier_output[i] = 1.0; // -- used to visualize surface points (blue in the paper)
}
else
{
// -- correct false positives to inner points
m_mc_normals[i] = Vector3r::Zero();
m_mc_curv[i] = 0.0;
m_classifier_output[i] = 0.5; // -- used for visualize post-correction points (white in the paper)
}
}
else
{
// -- used to visualize inner points (green in the paper)
m_classifier_output[i] = 0.0;
}
}
}
// ################################################################################################
// ## second pass, compute normals and curvature and compute PCA normal
// ################################################################################################
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
if (m_mc_normals[i] != Vector3r::Zero())
{
const Vector3r& xi = m_model->getPosition( i );
Vector3r normalCorrection = Vector3r::Zero();
Vector3r& ai = m_model->getAcceleration( i );
Real correctionForCurvature = 0;
Real correctionFactor = 0.0;
Vector3r centroid = xi;
Vector3r surfCentDir = Vector3r::Zero();
// collect neighbors
multimap<Real, size_t> neighs;
Eigen::Matrix<Real, 3, 3> t = Eigen::Matrix<Real, 3, 3>::Zero();
int t_count = 0;
Vector3r neighCent = Vector3r::Zero();
int nrNeighhbors = sim->numberOfNeighbors(fluidModelIndex, fluidModelIndex, i );
forall_fluid_neighbors_in_same_phase(
if (m_mc_normals[neighborIndex] != Vector3r::Zero())
{
Vector3r& xj = m_model->getPosition( neighborIndex );
Vector3r xjxi = (xj - xi);
surfCentDir += xjxi;
centroid += xj;
t_count++;
Real distanceji = xjxi.norm();
// collect neighbors sorted by distance relative to xi as estimate
if ( m_normal_mode != NormalMethod::MC )
{
Real distanceji = xjxi.norm();
neighs.insert( { distanceji, neighborIndex } );
}
normalCorrection += m_mc_normals[neighborIndex] * (1 - distanceji / supportRadius);
correctionForCurvature += m_mc_curv[neighborIndex] * (1 - distanceji / supportRadius);
correctionFactor += (1 - distanceji / supportRadius);
}
else if( m_normal_mode != NormalMethod::MC
&& classifyParticleConfigurable( m_classifier_input[neighborIndex], nrNeighhbors, m_class_d_off ))
{
Vector3r& xj = m_model->getPosition( neighborIndex );
Vector3r xjxi = (xj - xi);
surfCentDir += xjxi;
centroid += xj;
t_count++;
Real distanceji = xjxi.norm();
neighs.insert( { distanceji, neighborIndex } );
} )
bool do_pca = false;
if (m_normal_mode == NormalMethod::PCA)
{
do_pca = true;
}
else if (m_normal_mode == NormalMethod::MIX)
{
if (m_pca_N_mix >= 0.0 && m_pca_C_mix >= 0.0)
do_pca = true;
}
if (do_pca)
{
centroid /= static_cast<Real>(t_count + 1);
surfCentDir.normalize();
const size_t neigh_limit = m_neighs_limit;
size_t neigh_count = 0;
for (pair<Real, size_t> nn : neighs)
{
size_t j = nn.second;
Vector3r& xj = m_model->getPosition( j );
Vector3r xjxi = (xj - centroid);
Real distanceji = xjxi.norm();
Real w = CubicKernel::W( distanceji ) / CubicKernel::W_zero();
neighCent += w * xj;
Eigen::Matrix<Real, 3, 3> dy = xjxi * xjxi.transpose();
t = t + w * dy;
t_count++;
if (neigh_count >= neigh_limit - 1)
break;
neigh_count++;
}
if (neighs.size() >= 4)
{
Vector3r pdt_ev;
Matrix3r pdt_ec;
MathFunctions::eigenDecomposition(t, pdt_ec, pdt_ev);
// sort values smallest to greatest
if (pdt_ev(0) > pdt_ev(1))
{
swap(pdt_ev(0), pdt_ev(1));
swap(pdt_ec(0, 0), pdt_ec(0, 1));
swap(pdt_ec(1, 0), pdt_ec(1, 1));
swap(pdt_ec(2, 0), pdt_ec(2, 1));
}
if (pdt_ev(0) > pdt_ev(2))
{
swap(pdt_ev(0), pdt_ev(2));
swap(pdt_ec(0, 0), pdt_ec(0, 2));
swap(pdt_ec(1, 0), pdt_ec(1, 2));
swap(pdt_ec(2, 0), pdt_ec(2, 2));
}
if (pdt_ev(1) > pdt_ev(2))
{
swap(pdt_ev(1), pdt_ev(2));
swap(pdt_ec(0, 1), pdt_ec(0, 2));
swap(pdt_ec(1, 1), pdt_ec(1, 2));
swap(pdt_ec(2, 1), pdt_ec(2, 2));
}
Vector3r minor = Vector3r(pdt_ec(0, 0), pdt_ec(1, 0), pdt_ec(2, 0));
if (minor.dot(m_mc_normals[i]) < 0.0)
m_pca_normals[i] = -1.0 * minor;
else
m_pca_normals[i] = minor;
m_pca_curv[i] = static_cast<Real>(3.0) * pdt_ev(0) / (pdt_ev(0) + pdt_ev(1) + pdt_ev(2) );
if (surfCentDir.dot(m_pca_normals[i]) > 0.0)
m_pca_curv[i] *= -1;
m_pca_normals[i].normalize();
}
else
{
m_pca_normals[i] = Vector3r::Zero();
m_pca_curv[i] = 0.0;
}
}
normalCorrection.normalize();
m_mc_normals_smooth[i] = (1 - m_tau) * m_mc_normals[i] + m_tau * normalCorrection;
m_mc_normals_smooth[i].normalize();
m_mc_curv_smooth[i] =
((static_cast<Real>(1.0) - m_tau) * m_mc_curv[i] + m_tau * correctionForCurvature) /
((static_cast<Real>(1.0) - m_tau) + m_tau * correctionFactor);
}
}
}
// ################################################################################################
// ## third pass, final blending and temporal smoothing
// ################################################################################################
m_CS_smooth_passes = std::max( 1, m_CS_smooth_passes );
for (int si = 0; si < m_CS_smooth_passes; si++)
{
// smoothing pass 2 for sphericity
#pragma omp parallel default(shared)
{
#pragma omp for schedule(static)
for (int i = 0; i < (int)numParticles; i++)
{
if (m_mc_normals[i] != Vector3r::Zero())
{
int count = 0;
Real CsCorr = 0.0;
const Vector3r& xi = m_model->getPosition( i );
forall_fluid_neighbors_in_same_phase(
if (m_mc_normals[neighborIndex] != Vector3r::Zero())
{
CsCorr += m_pca_curv[neighborIndex];
count++;
} )
if (count > 0)
m_pca_curv_smooth[i] = static_cast<Real>(0.25) * m_pca_curv_smooth[i] + static_cast<Real>(0.75) * CsCorr / static_cast<Real>(count);
else
m_pca_curv_smooth[i] = m_pca_curv[i];
m_pca_curv_smooth[i] /= supportRadius;
m_pca_curv_smooth[i] *= 20.0;
if (m_pca_curv_smooth[i] > 0.0)
m_pca_curv_smooth[i] = std::min( 0.5f / supportRadius, m_pca_curv_smooth[i] );
else
m_pca_curv_smooth[i] = std::max( -0.5f / supportRadius, m_pca_curv_smooth[i] );
Vector3r final_normal = Vector3r::Zero();
Real final_curvature = m_mc_curv_smooth[i];
if (m_normal_mode == NormalMethod::MC)
{
final_normal = m_mc_normals_smooth[i];
final_curvature = m_mc_curv_smooth[i];
}
else if (m_normal_mode == NormalMethod::PCA)
{
final_normal = m_pca_normals[i];
final_curvature = m_pca_curv_smooth[i];
}
else if (m_normal_mode == NormalMethod::MIX) // <------------------------------
{
bool no_N = (m_pca_normals[i] == Vector3r::Zero());
bool no_C = no_N || (m_pca_curv_smooth[i] != m_pca_curv_smooth[i]);
if (no_N)
final_normal = m_mc_normals_smooth[i];
else
final_normal = m_pca_N_mix * m_pca_normals[i] + (1.0 - m_pca_N_mix) * m_mc_normals_smooth[i];
final_normal.normalize();
if (no_C)
m_pca_curv_smooth[i] = m_mc_curv_smooth[i];
else
{
if (m_mc_curv_smooth[i] < 0.0)
final_curvature = m_mc_curv_smooth[i];
else
final_curvature =
m_pca_C_mix * m_pca_curv_smooth[i] +
(static_cast<Real>(1.0) - m_pca_C_mix) * m_mc_curv_smooth[i];
}
}
if (m_temporal_smoothing)
m_final_curvatures[i] = static_cast<Real>(0.05) * final_curvature + static_cast<Real>(0.95) * m_final_curvatures_old[i];
else
m_final_curvatures[i] = final_curvature;
Vector3r force = final_normal * k * m_final_curvatures[i];
Vector3r& ai = m_model->getAcceleration( i );
ai -= (1 / m_model->getMass( i )) * force;
m_final_curvatures_old[i] = m_final_curvatures[i];
}
else // non surface particle blend 0.0 curvature
{
if (m_temporal_smoothing)
m_final_curvatures[i] = static_cast<Real>(0.95) * m_final_curvatures_old[i];
else
m_final_curvatures[i] = 0.0;
m_final_curvatures_old[i] = m_final_curvatures[i];
}
}
}
}
}
void SurfaceTension_ZorillaRitter2020::reset()
{
}