openfoam/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriverFeature.C
Mark Olesen b0b4c1aae5 COMP: intel compiler issues with operator ""_deg (fixes #544)
- this represents a partial revert for commit 6a0a8b99b3
2017-07-21 16:40:31 +02:00

4210 lines
126 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2011-2015 OpenFOAM Foundation
\\/ M anipulation | Copyright (C) 2015 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "snappySnapDriver.H"
#include "polyTopoChange.H"
#include "syncTools.H"
#include "fvMesh.H"
#include "OBJstream.H"
#include "motionSmoother.H"
#include "refinementSurfaces.H"
#include "refinementFeatures.H"
#include "unitConversion.H"
#include "plane.H"
#include "featureEdgeMesh.H"
#include "treeDataPoint.H"
#include "indexedOctree.H"
#include "snapParameters.H"
#include "PatchTools.H"
#include "pyramidPointFaceRef.H"
#include "localPointRegion.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
namespace Foam
{
template<class T>
class listPlusEqOp
{
public:
void operator()(List<T>& x, const List<T>& y) const
{
label sz = x.size();
x.setSize(sz+y.size());
forAll(y, i)
{
x[sz++] = y[i];
}
}
};
}
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
bool Foam::snappySnapDriver::isFeaturePoint
(
const scalar featureCos,
const indirectPrimitivePatch& pp,
const PackedBoolList& isFeatureEdge,
const label pointi
) const
{
const pointField& points = pp.localPoints();
const edgeList& edges = pp.edges();
const labelList& pEdges = pp.pointEdges()[pointi];
label nFeatEdges = 0;
forAll(pEdges, i)
{
if (isFeatureEdge[pEdges[i]])
{
nFeatEdges++;
for (label j = i+1; j < pEdges.size(); j++)
{
if (isFeatureEdge[pEdges[j]])
{
const edge& ei = edges[pEdges[i]];
const edge& ej = edges[pEdges[j]];
const point& p = points[pointi];
const point& pi = points[ei.otherVertex(pointi)];
const point& pj = points[ej.otherVertex(pointi)];
vector vi = p-pi;
scalar viMag = mag(vi);
vector vj = pj-p;
scalar vjMag = mag(vj);
if
(
viMag > SMALL
&& vjMag > SMALL
&& ((vi/viMag & vj/vjMag) < featureCos)
)
{
return true;
}
}
}
}
}
if (nFeatEdges == 1)
{
// End of feature-edge string
return true;
}
return false;
}
void Foam::snappySnapDriver::smoothAndConstrain
(
const PackedBoolList& isPatchMasterEdge,
const indirectPrimitivePatch& pp,
const labelList& meshEdges,
const List<pointConstraint>& constraints,
vectorField& disp
) const
{
const fvMesh& mesh = meshRefiner_.mesh();
for (label avgIter = 0; avgIter < 20; avgIter++)
{
// Calculate average displacement of neighbours
// - unconstrained (i.e. surface) points use average of all
// neighbouring points
// - from testing it has been observed that it is not beneficial
// to have edge constrained points use average of all edge or point
// constrained neighbours since they're already attracted to
// the nearest point on the feature.
// Having them attract to point-constrained neighbours does not
// make sense either since there is usually just one of them so
// it severely distorts it.
// - same for feature points. They are already attracted to the
// nearest feature point.
vectorField dispSum(pp.nPoints(), Zero);
labelList dispCount(pp.nPoints(), 0);
const labelListList& pointEdges = pp.pointEdges();
const edgeList& edges = pp.edges();
forAll(pointEdges, pointi)
{
const labelList& pEdges = pointEdges[pointi];
label nConstraints = constraints[pointi].first();
if (nConstraints <= 1)
{
forAll(pEdges, i)
{
label edgei = pEdges[i];
if (isPatchMasterEdge[edgei])
{
label nbrPointi = edges[edgei].otherVertex(pointi);
if (constraints[nbrPointi].first() >= nConstraints)
{
dispSum[pointi] += disp[nbrPointi];
dispCount[pointi]++;
}
}
}
}
}
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
dispSum,
plusEqOp<point>(),
vector::zero,
mapDistribute::transform()
);
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
dispCount,
plusEqOp<label>(),
label(0),
mapDistribute::transform()
);
// Constraints
forAll(constraints, pointi)
{
if (dispCount[pointi] > 0)
{
// Mix my displacement with neighbours' displacement
disp[pointi] =
0.5
*(disp[pointi] + dispSum[pointi]/dispCount[pointi]);
}
}
}
}
void Foam::snappySnapDriver::calcNearestFace
(
const label iter,
const indirectPrimitivePatch& pp,
const scalarField& faceSnapDist,
vectorField& faceDisp,
vectorField& faceSurfaceNormal,
labelList& faceSurfaceGlobalRegion
//vectorField& faceRotation
) const
{
const fvMesh& mesh = meshRefiner_.mesh();
const refinementSurfaces& surfaces = meshRefiner_.surfaces();
// Displacement and orientation per pp face.
faceDisp.setSize(pp.size());
faceDisp = Zero;
faceSurfaceNormal.setSize(pp.size());
faceSurfaceNormal = Zero;
faceSurfaceGlobalRegion.setSize(pp.size());
faceSurfaceGlobalRegion = -1;
// Divide surfaces into zoned and unzoned
const labelList zonedSurfaces =
surfaceZonesInfo::getNamedSurfaces(surfaces.surfZones());
const labelList unzonedSurfaces =
surfaceZonesInfo::getUnnamedSurfaces(surfaces.surfZones());
// Per pp face the current surface snapped to
labelList snapSurf(pp.size(), -1);
// Do zoned surfaces
// ~~~~~~~~~~~~~~~~~
// Zoned faces only attract to corresponding surface
// Extract faces per zone
const PtrList<surfaceZonesInfo>& surfZones = surfaces.surfZones();
forAll(zonedSurfaces, i)
{
label zoneSurfi = zonedSurfaces[i];
const word& faceZoneName = surfZones[zoneSurfi].faceZoneName();
// Get indices of faces on pp that are also in zone
label zonei = mesh.faceZones().findZoneID(faceZoneName);
if (zonei == -1)
{
FatalErrorInFunction
<< "Problem. Cannot find zone " << faceZoneName
<< exit(FatalError);
}
const faceZone& fZone = mesh.faceZones()[zonei];
PackedBoolList isZonedFace(mesh.nFaces());
forAll(fZone, i)
{
isZonedFace[fZone[i]] = 1;
}
DynamicList<label> ppFaces(fZone.size());
DynamicList<label> meshFaces(fZone.size());
forAll(pp.addressing(), i)
{
if (isZonedFace[pp.addressing()[i]])
{
snapSurf[i] = zoneSurfi;
ppFaces.append(i);
meshFaces.append(pp.addressing()[i]);
}
}
//Pout<< "For faceZone " << fZone.name()
// << " found " << ppFaces.size() << " out of " << pp.size()
// << endl;
pointField fc
(
indirectPrimitivePatch
(
IndirectList<face>(mesh.faces(), meshFaces),
mesh.points()
).faceCentres()
);
List<pointIndexHit> hitInfo;
labelList hitSurface;
labelList hitRegion;
vectorField hitNormal;
surfaces.findNearestRegion
(
labelList(1, zoneSurfi),
fc,
sqr(faceSnapDist),// sqr of attract dist
hitSurface,
hitInfo,
hitRegion,
hitNormal
);
forAll(hitInfo, hiti)
{
if (hitInfo[hiti].hit())
{
label facei = ppFaces[hiti];
faceDisp[facei] = hitInfo[hiti].hitPoint() - fc[hiti];
faceSurfaceNormal[facei] = hitNormal[hiti];
faceSurfaceGlobalRegion[facei] = surfaces.globalRegion
(
hitSurface[hiti],
hitRegion[hiti]
);
}
else
{
WarningInFunction
<< "Did not find surface near face centre " << fc[hiti]
<< endl;
}
}
}
// Do unzoned surfaces
// ~~~~~~~~~~~~~~~~~~~
// Unzoned faces attract to any unzoned surface
DynamicList<label> ppFaces(pp.size());
DynamicList<label> meshFaces(pp.size());
forAll(pp.addressing(), i)
{
if (snapSurf[i] == -1)
{
ppFaces.append(i);
meshFaces.append(pp.addressing()[i]);
}
}
//Pout<< "Found " << ppFaces.size() << " unzoned faces out of "
// << pp.size() << endl;
pointField fc
(
indirectPrimitivePatch
(
IndirectList<face>(mesh.faces(), meshFaces),
mesh.points()
).faceCentres()
);
List<pointIndexHit> hitInfo;
labelList hitSurface;
labelList hitRegion;
vectorField hitNormal;
surfaces.findNearestRegion
(
unzonedSurfaces,
fc,
sqr(faceSnapDist),// sqr of attract dist
hitSurface,
hitInfo,
hitRegion,
hitNormal
);
forAll(hitInfo, hiti)
{
if (hitInfo[hiti].hit())
{
label facei = ppFaces[hiti];
faceDisp[facei] = hitInfo[hiti].hitPoint() - fc[hiti];
faceSurfaceNormal[facei] = hitNormal[hiti];
faceSurfaceGlobalRegion[facei] = surfaces.globalRegion
(
hitSurface[hiti],
hitRegion[hiti]
);
}
else
{
WarningInFunction
<< "Did not find surface near face centre " << fc[hiti]
<< endl;
}
}
//// Determine rotation
//// ~~~~~~~~~~~~~~~~~~
//
//// Determine rotation axis
//faceRotation.setSize(pp.size());
//faceRotation = Zero;
//
//forAll(faceRotation, facei)
//{
// // Note: extend to >180 degrees checking
// faceRotation[facei] =
// pp.faceNormals()[facei]
// ^ faceSurfaceNormal[facei];
//}
//
//if (debug&meshRefinement::ATTRACTION)
//{
// dumpMove
// (
// mesh.time().path()
// / "faceDisp_" + name(iter) + ".obj",
// pp.faceCentres(),
// pp.faceCentres() + faceDisp
// );
// dumpMove
// (
// mesh.time().path()
// / "faceRotation_" + name(iter) + ".obj",
// pp.faceCentres(),
// pp.faceCentres() + faceRotation
// );
//}
}
// Collect (possibly remote) per point data of all surrounding faces
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// - faceSurfaceNormal
// - faceDisp
// - faceCentres&faceNormal
void Foam::snappySnapDriver::calcNearestFacePointProperties
(
const label iter,
const indirectPrimitivePatch& pp,
const vectorField& faceDisp,
const vectorField& faceSurfaceNormal,
const labelList& faceSurfaceGlobalRegion,
List<List<point>>& pointFaceSurfNormals,
List<List<point>>& pointFaceDisp,
List<List<point>>& pointFaceCentres,
List<labelList>& pointFacePatchID
) const
{
const fvMesh& mesh = meshRefiner_.mesh();
const PackedBoolList isMasterFace(syncTools::getMasterFaces(mesh));
// For now just get all surrounding face data. Expensive - should just
// store and sync data on coupled points only
// (see e.g PatchToolsNormals.C)
pointFaceSurfNormals.setSize(pp.nPoints());
pointFaceDisp.setSize(pp.nPoints());
pointFaceCentres.setSize(pp.nPoints());
pointFacePatchID.setSize(pp.nPoints());
// Fill local data
forAll(pp.pointFaces(), pointi)
{
const labelList& pFaces = pp.pointFaces()[pointi];
// Count valid face normals
label nFaces = 0;
forAll(pFaces, i)
{
label facei = pFaces[i];
if (isMasterFace[facei] && faceSurfaceGlobalRegion[facei] != -1)
{
nFaces++;
}
}
List<point>& pNormals = pointFaceSurfNormals[pointi];
pNormals.setSize(nFaces);
List<point>& pDisp = pointFaceDisp[pointi];
pDisp.setSize(nFaces);
List<point>& pFc = pointFaceCentres[pointi];
pFc.setSize(nFaces);
labelList& pFid = pointFacePatchID[pointi];
pFid.setSize(nFaces);
nFaces = 0;
forAll(pFaces, i)
{
label facei = pFaces[i];
label globalRegioni = faceSurfaceGlobalRegion[facei];
if (isMasterFace[facei] && globalRegioni != -1)
{
pNormals[nFaces] = faceSurfaceNormal[facei];
pDisp[nFaces] = faceDisp[facei];
pFc[nFaces] = pp.faceCentres()[facei];
pFid[nFaces] = globalToMasterPatch_[globalRegioni];
nFaces++;
}
}
}
// Collect additionally 'normal' boundary faces for boundaryPoints of pp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// points on the boundary of pp should pick up non-pp normals
// as well for the feature-reconstruction to behave correctly.
// (the movement is already constrained outside correctly so it
// is only that the unconstrained attraction vector is calculated
// correctly)
{
const polyBoundaryMesh& pbm = mesh.boundaryMesh();
labelList patchID(pbm.patchID());
// Unmark all non-coupled boundary faces
forAll(pbm, patchi)
{
const polyPatch& pp = pbm[patchi];
if (pp.coupled() || isA<emptyPolyPatch>(pp))
{
forAll(pp, i)
{
label meshFacei = pp.start()+i;
patchID[meshFacei-mesh.nInternalFaces()] = -1;
}
}
}
// Remove any meshed faces
forAll(pp.addressing(), i)
{
label meshFacei = pp.addressing()[i];
patchID[meshFacei-mesh.nInternalFaces()] = -1;
}
// See if edge of pp uses any non-meshed boundary faces. If so add the
// boundary face as additional constraint. Note that we account for
// both 'real' boundary edges and boundary edge of baffles
const labelList bafflePair
(
localPointRegion::findDuplicateFaces(mesh, pp.addressing())
);
// Mark all points on 'boundary' edges
PackedBoolList isBoundaryPoint(pp.nPoints());
const labelListList& edgeFaces = pp.edgeFaces();
const edgeList& edges = pp.edges();
forAll(edgeFaces, edgei)
{
const edge& e = edges[edgei];
const labelList& eFaces = edgeFaces[edgei];
if (eFaces.size() == 1)
{
// 'real' boundary edge
isBoundaryPoint[e[0]] = true;
isBoundaryPoint[e[1]] = true;
}
else if (eFaces.size() == 2 && bafflePair[eFaces[0]] == eFaces[1])
{
// 'baffle' boundary edge
isBoundaryPoint[e[0]] = true;
isBoundaryPoint[e[1]] = true;
}
}
// Construct labelList equivalent of meshPointMap
labelList meshToPatchPoint(mesh.nPoints(), -1);
forAll(pp.meshPoints(), pointi)
{
meshToPatchPoint[pp.meshPoints()[pointi]] = pointi;
}
forAll(patchID, bFacei)
{
label patchi = patchID[bFacei];
if (patchi != -1)
{
label facei = mesh.nInternalFaces()+bFacei;
const face& f = mesh.faces()[facei];
forAll(f, fp)
{
label pointi = meshToPatchPoint[f[fp]];
if (pointi != -1 && isBoundaryPoint[pointi])
{
List<point>& pNormals = pointFaceSurfNormals[pointi];
List<point>& pDisp = pointFaceDisp[pointi];
List<point>& pFc = pointFaceCentres[pointi];
labelList& pFid = pointFacePatchID[pointi];
const point& pt = mesh.points()[f[fp]];
vector fn = mesh.faceAreas()[facei];
pNormals.append(fn/mag(fn));
pDisp.append(mesh.faceCentres()[facei]-pt);
pFc.append(mesh.faceCentres()[facei]);
pFid.append(patchi);
}
}
}
}
}
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
pointFaceSurfNormals,
listPlusEqOp<point>(),
List<point>(),
mapDistribute::transform()
);
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
pointFaceDisp,
listPlusEqOp<point>(),
List<point>(),
mapDistribute::transform()
);
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
pointFaceCentres,
listPlusEqOp<point>(),
List<point>(),
mapDistribute::transformPosition()
);
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
pointFacePatchID,
listPlusEqOp<label>(),
List<label>()
);
// Sort the data according to the face centres. This is only so we get
// consistent behaviour serial and parallel.
labelList visitOrder;
forAll(pointFaceDisp, pointi)
{
List<point>& pNormals = pointFaceSurfNormals[pointi];
List<point>& pDisp = pointFaceDisp[pointi];
List<point>& pFc = pointFaceCentres[pointi];
labelList& pFid = pointFacePatchID[pointi];
sortedOrder(mag(pFc)(), visitOrder);
pNormals = List<point>(pNormals, visitOrder);
pDisp = List<point>(pDisp, visitOrder);
pFc = List<point>(pFc, visitOrder);
pFid = UIndirectList<label>(pFid, visitOrder)();
}
}
// Gets passed in offset to nearest point on feature edge. Calculates
// if the point has a different number of faces on either side of the feature
// and if so attracts the point to that non-dominant plane.
void Foam::snappySnapDriver::correctAttraction
(
const DynamicList<point>& surfacePoints,
const DynamicList<label>& surfaceCounts,
const point& edgePt,
const vector& edgeNormal, // normalised normal
const point& pt,
vector& edgeOffset // offset from pt to point on edge
) const
{
// Tangential component along edge
scalar tang = ((pt-edgePt)&edgeNormal);
labelList order;
Foam::sortedOrder(surfaceCounts, order);
if (order[0] < order[1])
{
// There is a non-dominant plane. Use the point on the plane to
// attract to.
vector attractD = surfacePoints[order[0]]-edgePt;
// Tangential component along edge
scalar tang2 = (attractD&edgeNormal);
// Normal component
attractD -= tang2*edgeNormal;
// Calculate fraction of normal distances
scalar magAttractD = mag(attractD);
scalar fraction = magAttractD/(magAttractD+mag(edgeOffset));
point linePt =
edgePt
+ ((1.0-fraction)*tang2 + fraction*tang)*edgeNormal;
edgeOffset = linePt-pt;
}
}
Foam::pointIndexHit Foam::snappySnapDriver::findMultiPatchPoint
(
const point& pt,
const labelList& patchIDs,
const List<point>& faceCentres
) const
{
// Determine if multiple patchIDs
if (patchIDs.size())
{
label patch0 = patchIDs[0];
for (label i = 1; i < patchIDs.size(); i++)
{
if (patchIDs[i] != patch0)
{
return pointIndexHit(true, pt, labelMax);
}
}
}
return pointIndexHit(false, Zero, labelMax);
}
Foam::label Foam::snappySnapDriver::findNormal
(
const scalar featureCos,
const vector& n,
const DynamicList<vector>& surfaceNormals
) const
{
label index = -1;
forAll(surfaceNormals, j)
{
scalar cosAngle = (n&surfaceNormals[j]);
if
(
(cosAngle >= featureCos)
|| (cosAngle < (-1+0.001)) // triangle baffles
)
{
index = j;
break;
}
}
return index;
}
// Detect multiple patches. Returns pointIndexHit:
// - false, index=-1 : single patch
// - true , index=0 : multiple patches but on different normals planes
// (so geometric feature edge is also a region edge)
// - true , index=1 : multiple patches on same normals plane i.e. flat region
// edge
Foam::pointIndexHit Foam::snappySnapDriver::findMultiPatchPoint
(
const point& pt,
const labelList& patchIDs,
const DynamicList<vector>& surfaceNormals,
const labelList& faceToNormalBin
) const
{
if (patchIDs.empty())
{
return pointIndexHit(false, pt, -1);
}
// Detect single patch situation (to avoid allocation)
label patch0 = patchIDs[0];
for (label i = 1; i < patchIDs.size(); i++)
{
if (patchIDs[i] != patch0)
{
patch0 = -1;
break;
}
}
if (patch0 >= 0)
{
// Single patch
return pointIndexHit(false, pt, -1);
}
else
{
if (surfaceNormals.size() == 1)
{
// Same normals plane, flat region edge.
return pointIndexHit(true, pt, 1);
}
else
{
// Detect per normals bin
labelList normalToPatch(surfaceNormals.size(), -1);
forAll(faceToNormalBin, i)
{
if (faceToNormalBin[i] != -1)
{
label& patch = normalToPatch[faceToNormalBin[i]];
if (patch == -1)
{
// First occurence
patch = patchIDs[i];
}
else if (patch == -2)
{
// Already marked as being on multiple patches
}
else if (patch != patchIDs[i])
{
// Mark as being on multiple patches
patch = -2;
}
}
}
forAll(normalToPatch, normali)
{
if (normalToPatch[normali] == -2)
{
// Multiple patches on same normals plane, flat region
// edge
return pointIndexHit(true, pt, 1);
}
}
// All patches on either side of geometric feature anyway
return pointIndexHit(true, pt, 0);
}
}
}
void Foam::snappySnapDriver::writeStats
(
const indirectPrimitivePatch& pp,
const PackedBoolList& isPatchMasterPoint,
const List<pointConstraint>& patchConstraints
) const
{
label nMasterPoints = 0;
label nPlanar = 0;
label nEdge = 0;
label nPoint = 0;
forAll(patchConstraints, pointi)
{
if (isPatchMasterPoint[pointi])
{
nMasterPoints++;
if (patchConstraints[pointi].first() == 1)
{
nPlanar++;
}
else if (patchConstraints[pointi].first() == 2)
{
nEdge++;
}
else if (patchConstraints[pointi].first() == 3)
{
nPoint++;
}
}
}
reduce(nMasterPoints, sumOp<label>());
reduce(nPlanar, sumOp<label>());
reduce(nEdge, sumOp<label>());
reduce(nPoint, sumOp<label>());
Info<< "total master points :" << nMasterPoints
<< " of which attracted to :" << nl
<< " feature point : " << nPoint << nl
<< " feature edge : " << nEdge << nl
<< " nearest surface : " << nPlanar << nl
<< " rest : " << nMasterPoints-nPoint-nEdge-nPlanar
<< nl
<< endl;
}
void Foam::snappySnapDriver::featureAttractionUsingReconstruction
(
const label iter,
const scalar featureCos,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const vectorField& nearestDisp,
const label pointi,
const List<List<point>>& pointFaceSurfNormals,
const List<List<point>>& pointFaceDisp,
const List<List<point>>& pointFaceCentres,
const labelListList& pointFacePatchID,
DynamicList<point>& surfacePoints,
DynamicList<vector>& surfaceNormals,
labelList& faceToNormalBin,
vector& patchAttraction,
pointConstraint& patchConstraint
) const
{
patchAttraction = Zero;
patchConstraint = pointConstraint();
const List<point>& pfSurfNormals = pointFaceSurfNormals[pointi];
const List<point>& pfDisp = pointFaceDisp[pointi];
const List<point>& pfCentres = pointFaceCentres[pointi];
// Bin according to surface normal
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//- Bins of differing normals:
// - one normal : flat(tish) surface
// - two normals : geometric feature edge
// - three normals: geometric feature point
// - four normals : too complex a feature
surfacePoints.clear();
surfaceNormals.clear();
//- From face to above normals bin
faceToNormalBin.setSize(pfDisp.size());
faceToNormalBin = -1;
forAll(pfSurfNormals, i)
{
const point& fc = pfCentres[i];
const vector& fSNormal = pfSurfNormals[i];
const vector& fDisp = pfDisp[i];
// What to do with very far attraction? For now just ignore the face
if (magSqr(fDisp) < sqr(snapDist[pointi]) && mag(fSNormal) > VSMALL)
{
const point pt = fc + fDisp;
// Do we already have surface normal?
faceToNormalBin[i] = findNormal
(
featureCos,
fSNormal,
surfaceNormals
);
if (faceToNormalBin[i] != -1)
{
// Same normal
}
else
{
// Now check if the planes go through the same edge or point
if (surfacePoints.size() <= 1)
{
surfacePoints.append(pt);
faceToNormalBin[i] = surfaceNormals.size();
surfaceNormals.append(fSNormal);
}
else if (surfacePoints.size() == 2)
{
plane pl0(surfacePoints[0], surfaceNormals[0]);
plane pl1(surfacePoints[1], surfaceNormals[1]);
plane::ray r(pl0.planeIntersect(pl1));
vector featureNormal = r.dir() / mag(r.dir());
if (mag(fSNormal&featureNormal) >= 0.001)
{
// Definitely makes a feature point
surfacePoints.append(pt);
faceToNormalBin[i] = surfaceNormals.size();
surfaceNormals.append(fSNormal);
}
}
else if (surfacePoints.size() == 3)
{
// Have already feature point. See if this new plane is
// the same point or not.
plane pl0(surfacePoints[0], surfaceNormals[0]);
plane pl1(surfacePoints[1], surfaceNormals[1]);
plane pl2(surfacePoints[2], surfaceNormals[2]);
point p012(pl0.planePlaneIntersect(pl1, pl2));
plane::ray r(pl0.planeIntersect(pl1));
vector featureNormal = r.dir() / mag(r.dir());
if (mag(fSNormal&featureNormal) >= 0.001)
{
plane pl3(pt, fSNormal);
point p013(pl0.planePlaneIntersect(pl1, pl3));
if (mag(p012-p013) > snapDist[pointi])
{
// Different feature point
surfacePoints.append(pt);
faceToNormalBin[i] = surfaceNormals.size();
surfaceNormals.append(fSNormal);
}
}
}
}
}
}
const point& pt = pp.localPoints()[pointi];
// Check the number of directions
if (surfaceNormals.size() == 1)
{
// Normal distance to plane
vector d =
((surfacePoints[0]-pt) & surfaceNormals[0])
*surfaceNormals[0];
// Trim to snap distance
if (magSqr(d) > sqr(snapDist[pointi]))
{
d *= Foam::sqrt(sqr(snapDist[pointi])/magSqr(d));
}
patchAttraction = d;
// Store constraints
patchConstraint.applyConstraint(surfaceNormals[0]);
}
else if (surfaceNormals.size() == 2)
{
plane pl0(surfacePoints[0], surfaceNormals[0]);
plane pl1(surfacePoints[1], surfaceNormals[1]);
plane::ray r(pl0.planeIntersect(pl1));
vector n = r.dir() / mag(r.dir());
// Get nearest point on infinite ray
vector d = r.refPoint()-pt;
d -= (d&n)*n;
// Trim to snap distance
if (magSqr(d) > sqr(snapDist[pointi]))
{
d *= Foam::sqrt(sqr(snapDist[pointi])/magSqr(d));
}
patchAttraction = d;
// Store constraints
patchConstraint.applyConstraint(surfaceNormals[0]);
patchConstraint.applyConstraint(surfaceNormals[1]);
}
else if (surfaceNormals.size() == 3)
{
// Calculate point from the faces.
plane pl0(surfacePoints[0], surfaceNormals[0]);
plane pl1(surfacePoints[1], surfaceNormals[1]);
plane pl2(surfacePoints[2], surfaceNormals[2]);
point cornerPt(pl0.planePlaneIntersect(pl1, pl2));
vector d = cornerPt - pt;
// Trim to snap distance
if (magSqr(d) > sqr(snapDist[pointi]))
{
d *= Foam::sqrt(sqr(snapDist[pointi])/magSqr(d));
}
patchAttraction = d;
// Store constraints
patchConstraint.applyConstraint(surfaceNormals[0]);
patchConstraint.applyConstraint(surfaceNormals[1]);
patchConstraint.applyConstraint(surfaceNormals[2]);
}
}
// Special version that calculates attraction in one go
void Foam::snappySnapDriver::featureAttractionUsingReconstruction
(
const label iter,
const scalar featureCos,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const vectorField& nearestDisp,
const List<List<point>>& pointFaceSurfNormals,
const List<List<point>>& pointFaceDisp,
const List<List<point>>& pointFaceCentres,
const labelListList& pointFacePatchID,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
autoPtr<OBJstream> feStr;
autoPtr<OBJstream> fpStr;
if (debug&meshRefinement::ATTRACTION)
{
feStr.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "implicitFeatureEdge_" + name(iter) + ".obj"
)
);
Info<< "Dumping implicit feature-edge direction to "
<< feStr().name() << endl;
fpStr.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "implicitFeaturePoint_" + name(iter) + ".obj"
)
);
Info<< "Dumping implicit feature-point direction to "
<< fpStr().name() << endl;
}
DynamicList<point> surfacePoints(4);
DynamicList<vector> surfaceNormals(4);
labelList faceToNormalBin;
forAll(pp.localPoints(), pointi)
{
vector attraction = Zero;
pointConstraint constraint;
featureAttractionUsingReconstruction
(
iter,
featureCos,
pp,
snapDist,
nearestDisp,
pointi,
pointFaceSurfNormals,
pointFaceDisp,
pointFaceCentres,
pointFacePatchID,
surfacePoints,
surfaceNormals,
faceToNormalBin,
attraction,
constraint
);
if
(
(constraint.first() > patchConstraints[pointi].first())
|| (
(constraint.first() == patchConstraints[pointi].first())
&& (magSqr(attraction) < magSqr(patchAttraction[pointi]))
)
)
{
patchAttraction[pointi] = attraction;
patchConstraints[pointi] = constraint;
const point& pt = pp.localPoints()[pointi];
if (patchConstraints[pointi].first() == 2 && feStr.valid())
{
feStr().write(linePointRef(pt, pt+patchAttraction[pointi]));
}
else if (patchConstraints[pointi].first() == 3 && fpStr.valid())
{
fpStr().write(linePointRef(pt, pt+patchAttraction[pointi]));
}
}
}
}
void Foam::snappySnapDriver::stringFeatureEdges
(
const label iter,
const scalar featureCos,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const vectorField& rawPatchAttraction,
const List<pointConstraint>& rawPatchConstraints,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
// Snap edges to feature edges
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Walk existing edges and snap remaining ones (that are marked as
// feature edges in rawPatchConstraints)
// What this does is fill in any faces where not all points
// on the face are being attracted:
/*
+
/ \
/ \
---+ +---
\ /
\ /
+
*/
// so the top and bottom will never get attracted since the nearest
// back from the feature edge will always be one of the left or right
// points since the face is diamond like. So here we walk the feature edges
// and add any non-attracted points.
while (true)
{
label nChanged = 0;
const labelListList& pointEdges = pp.pointEdges();
forAll(pointEdges, pointi)
{
if (patchConstraints[pointi].first() == 2)
{
const point& pt = pp.localPoints()[pointi];
const labelList& pEdges = pointEdges[pointi];
const vector& featVec = patchConstraints[pointi].second();
// Detect whether there are edges in both directions.
// (direction along the feature edge that is)
bool hasPos = false;
bool hasNeg = false;
forAll(pEdges, pEdgei)
{
const edge& e = pp.edges()[pEdges[pEdgei]];
label nbrPointi = e.otherVertex(pointi);
if (patchConstraints[nbrPointi].first() > 1)
{
const point& nbrPt = pp.localPoints()[nbrPointi];
const point featPt =
nbrPt + patchAttraction[nbrPointi];
const scalar cosAngle = (featVec & (featPt-pt));
if (cosAngle > 0)
{
hasPos = true;
}
else
{
hasNeg = true;
}
}
}
if (!hasPos || !hasNeg)
{
//Pout<< "**Detected feature string end at "
// << pp.localPoints()[pointi] << endl;
// No string. Assign best choice on either side
label bestPosPointi = -1;
scalar minPosDistSqr = GREAT;
label bestNegPointi = -1;
scalar minNegDistSqr = GREAT;
forAll(pEdges, pEdgei)
{
const edge& e = pp.edges()[pEdges[pEdgei]];
label nbrPointi = e.otherVertex(pointi);
if
(
patchConstraints[nbrPointi].first() <= 1
&& rawPatchConstraints[nbrPointi].first() > 1
)
{
const vector& nbrFeatVec =
rawPatchConstraints[pointi].second();
if (mag(featVec&nbrFeatVec) > featureCos)
{
// nbrPointi attracted to sameish feature
// Note: also check on position.
scalar d2 = magSqr
(
rawPatchAttraction[nbrPointi]
);
const point featPt =
pp.localPoints()[nbrPointi]
+ rawPatchAttraction[nbrPointi];
const scalar cosAngle =
(featVec & (featPt-pt));
if (cosAngle > 0)
{
if (!hasPos && d2 < minPosDistSqr)
{
minPosDistSqr = d2;
bestPosPointi = nbrPointi;
}
}
else
{
if (!hasNeg && d2 < minNegDistSqr)
{
minNegDistSqr = d2;
bestNegPointi = nbrPointi;
}
}
}
}
}
if (bestPosPointi != -1)
{
// Use reconstructed-feature attraction. Use only
// part of it since not sure...
//const point& bestPt =
// pp.localPoints()[bestPosPointi];
//Pout<< "**Overriding point " << bestPt
// << " on reconstructed feature edge at "
// << rawPatchAttraction[bestPosPointi]+bestPt
// << " to attracted-to-feature-edge." << endl;
patchAttraction[bestPosPointi] =
0.5*rawPatchAttraction[bestPosPointi];
patchConstraints[bestPosPointi] =
rawPatchConstraints[bestPosPointi];
nChanged++;
}
if (bestNegPointi != -1)
{
// Use reconstructed-feature attraction. Use only
// part of it since not sure...
//const point& bestPt =
// pp.localPoints()[bestNegPointi];
//Pout<< "**Overriding point " << bestPt
// << " on reconstructed feature edge at "
// << rawPatchAttraction[bestNegPointi]+bestPt
// << " to attracted-to-feature-edge." << endl;
patchAttraction[bestNegPointi] =
0.5*rawPatchAttraction[bestNegPointi];
patchConstraints[bestNegPointi] =
rawPatchConstraints[bestNegPointi];
nChanged++;
}
}
}
}
reduce(nChanged, sumOp<label>());
Info<< "Stringing feature edges : changed " << nChanged << " points"
<< endl;
if (nChanged == 0)
{
break;
}
}
}
void Foam::snappySnapDriver::releasePointsNextToMultiPatch
(
const label iter,
const scalar featureCos,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const List<List<point>>& pointFaceCentres,
const labelListList& pointFacePatchID,
const vectorField& rawPatchAttraction,
const List<pointConstraint>& rawPatchConstraints,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
autoPtr<OBJstream> multiPatchStr;
if (debug&meshRefinement::ATTRACTION)
{
multiPatchStr.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "multiPatch_" + name(iter) + ".obj"
)
);
Info<< "Dumping removed constraints due to same-face"
<< " multi-patch points to "
<< multiPatchStr().name() << endl;
}
// 1. Mark points on multiple patches
PackedBoolList isMultiPatchPoint(pp.size());
forAll(pointFacePatchID, pointi)
{
pointIndexHit multiPatchPt = findMultiPatchPoint
(
pp.localPoints()[pointi],
pointFacePatchID[pointi],
pointFaceCentres[pointi]
);
isMultiPatchPoint[pointi] = multiPatchPt.hit();
}
// 2. Make sure multi-patch points are also attracted
forAll(isMultiPatchPoint, pointi)
{
if (isMultiPatchPoint[pointi])
{
if
(
patchConstraints[pointi].first() <= 1
&& rawPatchConstraints[pointi].first() > 1
)
{
patchAttraction[pointi] = rawPatchAttraction[pointi];
patchConstraints[pointi] = rawPatchConstraints[pointi];
//if (multiPatchStr.valid())
//{
// Pout<< "Adding constraint on multiPatchPoint:"
// << pp.localPoints()[pointi]
// << " constraint:" << patchConstraints[pointi]
// << " attraction:" << patchAttraction[pointi]
// << endl;
//}
}
}
}
// Up to here it is all parallel ok.
// 3. Knock out any attraction on faces with multi-patch points
label nChanged = 0;
forAll(pp.localFaces(), facei)
{
const face& f = pp.localFaces()[facei];
label nMultiPatchPoints = 0;
forAll(f, fp)
{
label pointi = f[fp];
if
(
isMultiPatchPoint[pointi]
&& patchConstraints[pointi].first() > 1
)
{
nMultiPatchPoints++;
}
}
if (nMultiPatchPoints > 0)
{
forAll(f, fp)
{
label pointi = f[fp];
if
(
!isMultiPatchPoint[pointi]
&& patchConstraints[pointi].first() > 1
)
{
//Pout<< "Knocking out constraint"
// << " on non-multiPatchPoint:"
// << pp.localPoints()[pointi] << endl;
patchAttraction[pointi] = Zero;
patchConstraints[pointi] = pointConstraint();
nChanged++;
if (multiPatchStr.valid())
{
multiPatchStr().write(pp.localPoints()[pointi]);
}
}
}
}
}
reduce(nChanged, sumOp<label>());
Info<< "Removing constraints near multi-patch points : changed "
<< nChanged << " points" << endl;
}
Foam::labelPair Foam::snappySnapDriver::findDiagonalAttraction
(
const indirectPrimitivePatch& pp,
const vectorField& patchAttraction,
const List<pointConstraint>& patchConstraints,
const label facei
) const
{
const face& f = pp.localFaces()[facei];
// For now just detect any attraction. Improve this to look at
// actual attraction position and orientation
labelPair attractIndices(-1, -1);
if (f.size() >= 4)
{
for (label startFp = 0; startFp < f.size()-2; startFp++)
{
label minFp = f.rcIndex(startFp);
for
(
label endFp = f.fcIndex(f.fcIndex(startFp));
endFp < f.size() && endFp != minFp;
endFp++
)
{
if
(
patchConstraints[f[startFp]].first() >= 2
&& patchConstraints[f[endFp]].first() >= 2
)
{
attractIndices = labelPair(startFp, endFp);
break;
}
}
}
}
return attractIndices;
}
bool Foam::snappySnapDriver::isSplitAlignedWithFeature
(
const scalar featureCos,
const point& p0,
const pointConstraint& pc0,
const point& p1,
const pointConstraint& pc1
) const
{
vector d(p1-p0);
scalar magD = mag(d);
if (magD < VSMALL)
{
// Two diagonal points already colocated?
return false;
}
else
{
d /= magD;
// Is diagonal d aligned with at least one of the feature
// edges?
if (pc0.first() == 2 && mag(d & pc0.second()) > featureCos)
{
return true;
}
else if (pc1.first() == 2 && mag(d & pc1.second()) > featureCos)
{
return true;
}
else
{
return false;
}
}
}
// Is situation very concave
bool Foam::snappySnapDriver::isConcave
(
const point& c0,
const vector& area0,
const point& c1,
const vector& area1,
const scalar concaveCos
) const
{
vector n0 = area0;
scalar magN0 = mag(n0);
if (magN0 < VSMALL)
{
// Zero area face. What to return? For now disable splitting.
return true;
}
n0 /= magN0;
// Distance from c1 to plane of face0
scalar d = (c1-c0)&n0;
if (d <= 0)
{
// Convex (face1 centre on 'inside' of face0)
return false;
}
else
{
// Is a bit or very concave?
vector n1 = area1;
scalar magN1 = mag(n1);
if (magN1 < VSMALL)
{
// Zero area face. See above
return true;
}
n1 /= magN1;
if ((n0&n1) < concaveCos)
{
return true;
}
else
{
return false;
}
}
}
Foam::labelPair Foam::snappySnapDriver::findDiagonalAttraction
(
const scalar featureCos,
const scalar concaveCos,
const scalar minAreaRatio,
const indirectPrimitivePatch& pp,
const vectorField& patchAttr,
const List<pointConstraint>& patchConstraints,
const vectorField& nearestAttr,
const vectorField& nearestNormal,
const label facei,
DynamicField<point>& points0,
DynamicField<point>& points1
) const
{
const face& localF = pp.localFaces()[facei];
labelPair attractIndices(-1, -1);
if (localF.size() >= 4)
{
const pointField& localPts = pp.localPoints();
//// Estimate cell centre taking patchAttraction into account
//// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//// (is this necessary?)
//const polyMesh& mesh = meshRefiner_.mesh();
//label meshFacei = pp.addressing()[facei];
//const face& meshF = mesh.faces()[meshFacei];
//label celli = mesh.faceOwner()[meshFacei];
//const labelList& cPoints = mesh.cellPoints(celli);
//
//point cc(mesh.points()[meshF[0]]);
//for (label i = 1; i < meshF.size(); i++)
//{
// cc += mesh.points()[meshF[i]]+patchAttr[localF[i]];
//}
//forAll(cPoints, i)
//{
// label pointi = cPoints[i];
// if (findIndex(meshF, pointi) == -1)
// {
// cc += mesh.points()[pointi];
// }
//}
//cc /= cPoints.size();
////const point& cc = mesh.cellCentres()[celli];
//
//const scalar vol = pyrVol(pp, patchAttr, localF, cc);
//const scalar area = localF.mag(localPts);
// Try all diagonal cuts
// ~~~~~~~~~~~~~~~~~~~~~
face f0(3);
face f1(3);
for (label startFp = 0; startFp < localF.size()-2; startFp++)
{
label minFp = localF.rcIndex(startFp);
for
(
label endFp = localF.fcIndex(localF.fcIndex(startFp));
endFp < localF.size() && endFp != minFp;
endFp++
)
{
label startPti = localF[startFp];
label endPti = localF[endFp];
const pointConstraint& startPc = patchConstraints[startPti];
const pointConstraint& endPc = patchConstraints[endPti];
if (startPc.first() >= 2 && endPc.first() >= 2)
{
if (startPc.first() == 2 || endPc.first() == 2)
{
// Check if
// - sameish feature edge normal
// - diagonal aligned with feature edge normal
point start = localPts[startPti]+patchAttr[startPti];
point end = localPts[endPti]+patchAttr[endPti];
if
(
!isSplitAlignedWithFeature
(
featureCos,
start,
startPc,
end,
endPc
)
)
{
// Attract to different features. No need to
// introduce split
continue;
}
}
// Form two faces
// ~~~~~~~~~~~~~~
// Predict position of faces. End points of the faces
// attract to the feature
// and all the other points just attract to the nearest
// face0
f0.setSize(endFp-startFp+1);
label i0 = 0;
for (label fp = startFp; fp <= endFp; fp++)
{
f0[i0++] = localF[fp];
}
// Get compact face and points
const face compact0(identity(f0.size()));
points0.clear();
points0.append(localPts[f0[0]] + patchAttr[f0[0]]);
for (label fp=1; fp < f0.size()-1; fp++)
{
label pi = f0[fp];
points0.append(localPts[pi] + nearestAttr[pi]);
}
points0.append
(
localPts[f0.last()] + patchAttr[f0.last()]
);
// face1
f1.setSize(localF.size()+2-f0.size());
label i1 = 0;
for
(
label fp = endFp;
fp != startFp;
fp = localF.fcIndex(fp)
)
{
f1[i1++] = localF[fp];
}
f1[i1++] = localF[startFp];
// Get compact face and points
const face compact1(identity(f1.size()));
points1.clear();
points1.append(localPts[f1[0]] + patchAttr[f1[0]]);
for (label fp=1; fp < f1.size()-1; fp++)
{
label pi = f1[fp];
points1.append(localPts[pi] + nearestAttr[pi]);
}
points1.append
(
localPts[f1.last()] + patchAttr[f1.last()]
);
// Avoid splitting concave faces
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if
(
isConcave
(
compact0.centre(points0),
compact0.normal(points0),
compact1.centre(points1),
compact1.normal(points1),
concaveCos
)
)
{
//// Dump to obj
//Pout<< "# f0:" << f0 << endl;
//forAll(p, i)
//{
// meshTools::writeOBJ(Pout, points0[i]);
//}
//Pout<< "# f1:" << f1 << endl;
//forAll(p, i)
//{
// meshTools::writeOBJ(Pout, points1[i]);
//}
}
else
{
// Existing areas
const scalar area0 = f0.mag(localPts);
const scalar area1 = f1.mag(localPts);
if
(
area0/area1 >= minAreaRatio
&& area1/area0 >= minAreaRatio
)
{
attractIndices = labelPair(startFp, endFp);
}
}
}
}
}
}
return attractIndices;
}
void Foam::snappySnapDriver::splitDiagonals
(
const scalar featureCos,
const scalar concaveCos,
const scalar minAreaRatio,
const indirectPrimitivePatch& pp,
const vectorField& nearestAttraction,
const vectorField& nearestNormal,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints,
DynamicList<label>& splitFaces,
DynamicList<labelPair>& splits
) const
{
const labelList& bFaces = pp.addressing();
splitFaces.clear();
splitFaces.setCapacity(bFaces.size());
splits.clear();
splits.setCapacity(bFaces.size());
// Work arrays for storing points of face
DynamicField<point> facePoints0;
DynamicField<point> facePoints1;
forAll(bFaces, facei)
{
const labelPair split
(
findDiagonalAttraction
(
featureCos,
concaveCos,
minAreaRatio,
pp,
patchAttraction,
patchConstraints,
nearestAttraction,
nearestNormal,
facei,
facePoints0,
facePoints1
)
);
if (split != labelPair(-1, -1))
{
splitFaces.append(bFaces[facei]);
splits.append(split);
const face& f = pp.localFaces()[facei];
// Knock out other attractions on face
forAll(f, fp)
{
// Knock out any other constraints
if
(
fp != split[0]
&& fp != split[1]
&& patchConstraints[f[fp]].first() >= 2
)
{
patchConstraints[f[fp]] = pointConstraint
(
Tuple2<label, vector>
(
1,
nearestNormal[f[fp]]
)
);
patchAttraction[f[fp]] = nearestAttraction[f[fp]];
}
}
}
}
}
void Foam::snappySnapDriver::avoidDiagonalAttraction
(
const label iter,
const scalar featureCos,
const indirectPrimitivePatch& pp,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
forAll(pp.localFaces(), facei)
{
const face& f = pp.localFaces()[facei];
labelPair diag = findDiagonalAttraction
(
pp,
patchAttraction,
patchConstraints,
facei
);
if (diag[0] != -1 && diag[1] != -1)
{
// Found two diagonal points that being attracted.
// For now just attract my one to the average of those.
const label i0 = f[diag[0]];
const point pt0 =
pp.localPoints()[i0]+patchAttraction[i0];
const label i1 = f[diag[1]];
const point pt1 =
pp.localPoints()[i1]+patchAttraction[i1];
const point mid = 0.5*(pt0+pt1);
const scalar cosAngle = mag
(
patchConstraints[i0].second()
& patchConstraints[i1].second()
);
//Pout<< "Found diagonal attraction at indices:"
// << diag[0]
// << " and " << diag[1]
// << " with cosAngle:" << cosAngle
// << " mid:" << mid << endl;
if (cosAngle > featureCos)
{
// The two feature edges are roughly in the same direction.
// Add the nearest of the other points in the face as
// attractor
label minFp = -1;
scalar minDistSqr = GREAT;
forAll(f, fp)
{
label pointi = f[fp];
if (patchConstraints[pointi].first() <= 1)
{
const point& pt = pp.localPoints()[pointi];
scalar distSqr = magSqr(mid-pt);
if (distSqr < minDistSqr)
{
distSqr = minDistSqr;
minFp = fp;
}
}
}
if (minFp != -1)
{
label minPointi = f[minFp];
patchAttraction[minPointi] =
mid-pp.localPoints()[minPointi];
patchConstraints[minPointi] = patchConstraints[f[diag[0]]];
}
}
else
{
//Pout<< "Diagonal attractors at" << nl
// << " pt0:" << pt0
// << " constraint:"
// << patchConstraints[i0].second() << nl
// << " pt1:" << pt1
// << " constraint:"
// << patchConstraints[i1].second() << nl
// << " make too large an angle:"
// << mag
// (
// patchConstraints[i0].second()
// & patchConstraints[i1].second()
// )
// << endl;
}
}
}
}
Foam::Tuple2<Foam::label, Foam::pointIndexHit>
Foam::snappySnapDriver::findNearFeatureEdge
(
const bool isRegionEdge,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const label pointi,
const point& estimatedPt,
List<List<DynamicList<point>>>& edgeAttractors,
List<List<DynamicList<pointConstraint>>>& edgeConstraints,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
const refinementFeatures& features = meshRefiner_.features();
labelList nearEdgeFeat;
List<pointIndexHit> nearEdgeInfo;
vectorField nearNormal;
if (isRegionEdge)
{
features.findNearestRegionEdge
(
pointField(1, estimatedPt),
scalarField(1, sqr(snapDist[pointi])),
nearEdgeFeat,
nearEdgeInfo,
nearNormal
);
}
else
{
features.findNearestEdge
(
pointField(1, estimatedPt),
scalarField(1, sqr(snapDist[pointi])),
nearEdgeFeat,
nearEdgeInfo,
nearNormal
);
}
const pointIndexHit& nearInfo = nearEdgeInfo[0];
label feati = nearEdgeFeat[0];
if (nearInfo.hit())
{
// So we have a point on the feature edge. Use this
// instead of our estimate from planes.
edgeAttractors[feati][nearInfo.index()].append
(
nearInfo.hitPoint()
);
pointConstraint c(Tuple2<label, vector>(2, nearNormal[0]));
edgeConstraints[feati][nearInfo.index()].append(c);
// Store for later use
patchAttraction[pointi] = nearInfo.hitPoint()-pp.localPoints()[pointi];
patchConstraints[pointi] = c;
}
return Tuple2<label, pointIndexHit>(feati, nearInfo);
}
Foam::Tuple2<Foam::label, Foam::pointIndexHit>
Foam::snappySnapDriver::findNearFeaturePoint
(
const bool isRegionPoint,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const label pointi,
const point& estimatedPt,
// Feature-point to pp point
List<labelList>& pointAttractor,
List<List<pointConstraint>>& pointConstraints,
// Feature-edge to pp point
List<List<DynamicList<point>>>& edgeAttractors,
List<List<DynamicList<pointConstraint>>>& edgeConstraints,
// pp point to nearest feature
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
const refinementFeatures& features = meshRefiner_.features();
// Search for for featurePoints only! This ignores any non-feature points.
labelList nearFeat;
List<pointIndexHit> nearInfo;
features.findNearestPoint
(
pointField(1, estimatedPt),
scalarField(1, sqr(snapDist[pointi])),
nearFeat,
nearInfo
);
label feati = nearFeat[0];
if (feati != -1)
{
const point& pt = pp.localPoints()[pointi];
label featPointi = nearInfo[0].index();
const point& featPt = nearInfo[0].hitPoint();
scalar distSqr = magSqr(featPt-pt);
// Check if already attracted
label oldPointi = pointAttractor[feati][featPointi];
if (oldPointi != -1)
{
// Check distance
if (distSqr >= magSqr(featPt-pp.localPoints()[oldPointi]))
{
// oldPointi nearest. Keep.
feati = -1;
featPointi = -1;
}
else
{
// Current pointi nearer.
pointAttractor[feati][featPointi] = pointi;
pointConstraints[feati][featPointi].first() = 3;
pointConstraints[feati][featPointi].second() = Zero;
// Store for later use
patchAttraction[pointi] = featPt-pt;
patchConstraints[pointi] = pointConstraints[feati][featPointi];
// Reset oldPointi to nearest on feature edge
patchAttraction[oldPointi] = Zero;
patchConstraints[oldPointi] = pointConstraint();
findNearFeatureEdge
(
isRegionPoint, // search region edges only
pp,
snapDist,
oldPointi,
pp.localPoints()[oldPointi],
edgeAttractors,
edgeConstraints,
patchAttraction,
patchConstraints
);
}
}
else
{
// Current pointi nearer.
pointAttractor[feati][featPointi] = pointi;
pointConstraints[feati][featPointi].first() = 3;
pointConstraints[feati][featPointi].second() = Zero;
// Store for later use
patchAttraction[pointi] = featPt-pt;
patchConstraints[pointi] = pointConstraints[feati][featPointi];
}
}
return Tuple2<label, pointIndexHit>(feati, nearInfo[0]);
}
// Determines for every pp point - that is on multiple faces that form
// a feature - the nearest feature edge/point.
void Foam::snappySnapDriver::determineFeatures
(
const label iter,
const scalar featureCos,
const bool multiRegionFeatureSnap,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const vectorField& nearestDisp,
const List<List<point>>& pointFaceSurfNormals,
const List<List<point>>& pointFaceDisp,
const List<List<point>>& pointFaceCentres,
const labelListList& pointFacePatchID,
// Feature-point to pp point
List<labelList>& pointAttractor,
List<List<pointConstraint>>& pointConstraints,
// Feature-edge to pp point
List<List<DynamicList<point>>>& edgeAttractors,
List<List<DynamicList<pointConstraint>>>& edgeConstraints,
// pp point to nearest feature
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
autoPtr<OBJstream> featureEdgeStr;
autoPtr<OBJstream> missedEdgeStr;
autoPtr<OBJstream> featurePointStr;
autoPtr<OBJstream> missedMP0Str;
autoPtr<OBJstream> missedMP1Str;
if (debug&meshRefinement::ATTRACTION)
{
featureEdgeStr.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "featureEdge_" + name(iter) + ".obj"
)
);
Info<< "Dumping feature-edge sampling to "
<< featureEdgeStr().name() << endl;
missedEdgeStr.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "missedFeatureEdge_" + name(iter) + ".obj"
)
);
Info<< "Dumping feature-edges that are too far away to "
<< missedEdgeStr().name() << endl;
featurePointStr.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "featurePoint_" + name(iter) + ".obj"
)
);
Info<< "Dumping feature-point sampling to "
<< featurePointStr().name() << endl;
missedMP0Str.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "missedFeatureEdgeFromMPEdge_" + name(iter) + ".obj"
)
);
Info<< "Dumping region-edges that are too far away to "
<< missedMP0Str().name() << endl;
missedMP1Str.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "missedFeatureEdgeFromMPPoint_" + name(iter) + ".obj"
)
);
Info<< "Dumping region-points that are too far away to "
<< missedMP1Str().name() << endl;
}
DynamicList<point> surfacePoints(4);
DynamicList<vector> surfaceNormals(4);
labelList faceToNormalBin;
forAll(pp.localPoints(), pointi)
{
const point& pt = pp.localPoints()[pointi];
// Determine the geometric planes the point is (approximately) on.
// This is returned as a
// - attraction vector
// - and a constraint
// (1: attract to surface, constraint is normal of plane
// 2: attract to feature line, constraint is feature line direction
// 3: attract to feature point, constraint is zero)
vector attraction = Zero;
pointConstraint constraint;
featureAttractionUsingReconstruction
(
iter,
featureCos,
pp,
snapDist,
nearestDisp,
pointi,
pointFaceSurfNormals,
pointFaceDisp,
pointFaceCentres,
pointFacePatchID,
surfacePoints,
surfaceNormals,
faceToNormalBin,
attraction,
constraint
);
// Now combine the reconstruction with the current state of the
// point. The logic is quite complicated:
// - the new constraint (from reconstruction) will only win if
// - the constraint is higher (feature-point wins from feature-edge
// etc.)
// - or the constraint is the same but the attraction distance is less
//
// - then this will be combined with explicit searching on the
// features and optionally the analysis of the patches using the
// point. This analysis can do three thing:
// - the point is not on multiple patches
// - the point is on multiple patches but these are also
// different planes (so the region feature is also a geometric
// feature)
// - the point is on multiple patches some of which are on
// the same plane. This is the problem one - do we assume it is
// an additional constraint (feat edge upgraded to region point,
// see below)?
//
// Reconstruction MultiRegionFeatureSnap Attraction
// ------- ---------------------- -----------
// surface false surface
// surface true region edge
// feat edge false feat edge
// feat edge true and no planar regions feat edge
// feat edge true and yes planar regions region point
// feat point false feat point
// feat point true region point
if
(
(constraint.first() > patchConstraints[pointi].first())
|| (
(constraint.first() == patchConstraints[pointi].first())
&& (magSqr(attraction) < magSqr(patchAttraction[pointi]))
)
)
{
patchAttraction[pointi] = attraction;
patchConstraints[pointi] = constraint;
// Check the number of directions
if (patchConstraints[pointi].first() == 1)
{
// Flat surface. Check for different patchIDs
if (multiRegionFeatureSnap)
{
const point estimatedPt(pt + nearestDisp[pointi]);
pointIndexHit multiPatchPt
(
findMultiPatchPoint
(
estimatedPt,
pointFacePatchID[pointi],
surfaceNormals,
faceToNormalBin
)
);
if (multiPatchPt.hit())
{
// Behave like when having two surface normals so
// attract to nearest feature edge (with a guess for
// the multipatch point as starting point)
Tuple2<label, pointIndexHit> nearInfo =
findNearFeatureEdge
(
true, // isRegionEdge
pp,
snapDist,
pointi,
multiPatchPt.hitPoint(), // estimatedPt
edgeAttractors,
edgeConstraints,
patchAttraction,
patchConstraints
);
const pointIndexHit& info = nearInfo.second();
if (info.hit())
{
// Dump
if (featureEdgeStr.valid())
{
featureEdgeStr().write
(
linePointRef(pt, info.hitPoint())
);
}
}
else
{
if (missedEdgeStr.valid())
{
missedEdgeStr().write
(
linePointRef(pt, multiPatchPt.hitPoint())
);
}
}
}
}
}
else if (patchConstraints[pointi].first() == 2)
{
// Mark point on the nearest feature edge. Note that we
// only search within the surrounding since the plane
// reconstruction might find a feature where there isn't one.
const point estimatedPt(pt + patchAttraction[pointi]);
Tuple2<label, pointIndexHit> nearInfo(-1, pointIndexHit());
// Geometric feature edge. Check for different patchIDs
bool hasSnapped = false;
if (multiRegionFeatureSnap)
{
pointIndexHit multiPatchPt
(
findMultiPatchPoint
(
estimatedPt,
pointFacePatchID[pointi],
surfaceNormals,
faceToNormalBin
)
);
if (multiPatchPt.hit())
{
if (multiPatchPt.index() == 0)
{
// Region edge is also a geometric feature edge
nearInfo = findNearFeatureEdge
(
true, // isRegionEdge
pp,
snapDist,
pointi,
estimatedPt,
edgeAttractors,
edgeConstraints,
patchAttraction,
patchConstraints
);
hasSnapped = true;
// Debug: dump missed feature point
if
(
missedMP0Str.valid()
&& !nearInfo.second().hit()
)
{
missedMP0Str().write
(
linePointRef(pt, estimatedPt)
);
}
}
else
{
// One of planes of feature contains multiple
// regions. We assume (contentious!) that the
// separation between
// the regions is not aligned with the geometric
// feature so is an additional constraint on the
// point -> is region-feature-point.
nearInfo = findNearFeaturePoint
(
true, // isRegionPoint
pp,
snapDist,
pointi,
estimatedPt,
// Feature-point to pp point
pointAttractor,
pointConstraints,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// pp point to nearest feature
patchAttraction,
patchConstraints
);
hasSnapped = true;
// More contentious: if we don't find
// a near feature point we will never find the
// attraction to a feature edge either since
// the edgeAttractors/edgeConstraints do not get
// filled and we're using reverse attraction
// Note that we're in multiRegionFeatureSnap which
// where findMultiPatchPoint can decide the
// wrong thing. So: if failed finding a near
// feature point try for a feature edge
if (!nearInfo.second().hit())
{
nearInfo = findNearFeatureEdge
(
true, // isRegionEdge
pp,
snapDist,
pointi,
estimatedPt,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// pp point to nearest feature
patchAttraction,
patchConstraints
);
}
// Debug: dump missed feature point
if
(
missedMP1Str.valid()
&& !nearInfo.second().hit()
)
{
missedMP1Str().write
(
linePointRef(pt, estimatedPt)
);
}
}
}
}
if (!hasSnapped)
{
// Determine nearest point on feature edge. Store
// constraint
// (calculated from feature edge, alternative would be to
// use constraint calculated from both surfaceNormals)
nearInfo = findNearFeatureEdge
(
false, // isRegionPoint
pp,
snapDist,
pointi,
estimatedPt,
edgeAttractors,
edgeConstraints,
patchAttraction,
patchConstraints
);
hasSnapped = true;
}
// Dump to obj
const pointIndexHit& info = nearInfo.second();
if (info.hit())
{
if
(
patchConstraints[pointi].first() == 3
&& featurePointStr.valid()
)
{
featurePointStr().write
(
linePointRef(pt, info.hitPoint())
);
}
else if
(
patchConstraints[pointi].first() == 2
&& featureEdgeStr.valid()
)
{
featureEdgeStr().write
(
linePointRef(pt, info.hitPoint())
);
}
}
else
{
if (missedEdgeStr.valid())
{
missedEdgeStr().write
(
linePointRef(pt, estimatedPt)
);
}
}
}
else if (patchConstraints[pointi].first() == 3)
{
// Mark point on the nearest feature point.
const point estimatedPt(pt + patchAttraction[pointi]);
Tuple2<label, pointIndexHit> nearInfo(-1, pointIndexHit());
if (multiRegionFeatureSnap)
{
pointIndexHit multiPatchPt
(
findMultiPatchPoint
(
estimatedPt,
pointFacePatchID[pointi],
surfaceNormals,
faceToNormalBin
)
);
if (multiPatchPt.hit())
{
// Multiple regions
nearInfo = findNearFeaturePoint
(
true, // isRegionPoint
pp,
snapDist,
pointi,
estimatedPt,
// Feature-point to pp point
pointAttractor,
pointConstraints,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// pp point to nearest feature
patchAttraction,
patchConstraints
);
}
else
{
nearInfo = findNearFeaturePoint
(
false, // isRegionPoint
pp,
snapDist,
pointi,
estimatedPt,
// Feature-point to pp point
pointAttractor,
pointConstraints,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// pp point to nearest feature
patchAttraction,
patchConstraints
);
}
}
else
{
// No multi-patch snapping
nearInfo = findNearFeaturePoint
(
false, // isRegionPoint
pp,
snapDist,
pointi,
estimatedPt,
// Feature-point to pp point
pointAttractor,
pointConstraints,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// pp point to nearest feature
patchAttraction,
patchConstraints
);
}
const pointIndexHit& info = nearInfo.second();
if (info.hit() && featurePointStr.valid())
{
featurePointStr().write
(
linePointRef(pt, info.hitPoint())
);
}
}
}
}
}
// Baffle handling
// ~~~~~~~~~~~~~~~
// Override pointAttractor, edgeAttractor, patchAttration etc. to
// implement 'baffle' handling.
// Baffle: the mesh pp point originates from a loose standing
// baffle.
// Sampling the surface with the surrounding face-centres only picks up
// a single triangle normal so above determineFeatures will not have
// detected anything. So explicitly pick up feature edges on the pp
// (after duplicating points & smoothing so will already have been
// expanded) and match these to the features.
void Foam::snappySnapDriver::determineBaffleFeatures
(
const label iter,
const bool baffleFeaturePoints,
const scalar featureCos,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
// Feature-point to pp point
List<labelList>& pointAttractor,
List<List<pointConstraint>>& pointConstraints,
// Feature-edge to pp point
List<List<DynamicList<point>>>& edgeAttractors,
List<List<DynamicList<pointConstraint>>>& edgeConstraints,
// pp point to nearest feature
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
const fvMesh& mesh = meshRefiner_.mesh();
const refinementFeatures& features = meshRefiner_.features();
// Calculate edge-faces
List<List<point>> edgeFaceNormals(pp.nEdges());
// Fill local data
forAll(pp.edgeFaces(), edgei)
{
const labelList& eFaces = pp.edgeFaces()[edgei];
List<point>& eFc = edgeFaceNormals[edgei];
eFc.setSize(eFaces.size());
forAll(eFaces, i)
{
label facei = eFaces[i];
eFc[i] = pp.faceNormals()[facei];
}
}
{
// Precalculate mesh edges for pp.edges.
const labelList meshEdges
(
pp.meshEdges(mesh.edges(), mesh.pointEdges())
);
syncTools::syncEdgeList
(
mesh,
meshEdges,
edgeFaceNormals,
listPlusEqOp<point>(),
List<point>(),
mapDistribute::transform()
);
}
// Detect baffle edges. Assume initial mesh will have 0,90 or 180
// (baffle) degree angles so smoothing should make 0,90
// to be less than 90. Choose reasonable value
const scalar baffleFeatureCos = Foam::cos(degToRad(110.0));
autoPtr<OBJstream> baffleEdgeStr;
if (debug&meshRefinement::ATTRACTION)
{
baffleEdgeStr.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "baffleEdge_" + name(iter) + ".obj"
)
);
Info<< nl << "Dumping baffle-edges to "
<< baffleEdgeStr().name() << endl;
}
// Is edge on baffle
PackedBoolList isBaffleEdge(pp.nEdges());
label nBaffleEdges = 0;
// Is point on
// 0 : baffle-edge (0)
// 1 : baffle-feature-point (1)
// -1 : rest
labelList pointStatus(pp.nPoints(), -1);
forAll(edgeFaceNormals, edgei)
{
const List<point>& efn = edgeFaceNormals[edgei];
if (efn.size() == 2 && (efn[0]&efn[1]) < baffleFeatureCos)
{
isBaffleEdge[edgei] = true;
nBaffleEdges++;
const edge& e = pp.edges()[edgei];
pointStatus[e[0]] = 0;
pointStatus[e[1]] = 0;
if (baffleEdgeStr.valid())
{
const point& p0 = pp.localPoints()[e[0]];
const point& p1 = pp.localPoints()[e[1]];
baffleEdgeStr().write(linePointRef(p0, p1));
}
}
}
reduce(nBaffleEdges, sumOp<label>());
Info<< "Detected " << nBaffleEdges
<< " baffle edges out of "
<< returnReduce(pp.nEdges(), sumOp<label>())
<< " edges." << endl;
//- Baffle edges will be too ragged to sensibly determine feature points
//forAll(pp.pointEdges(), pointi)
//{
// if
// (
// isFeaturePoint
// (
// featureCos,
// pp,
// isBaffleEdge,
// pointi
// )
// )
// {
// //Pout<< "Detected feature point:" << pp.localPoints()[pointi]
// // << endl;
// //-TEMPORARILY DISABLED:
// //pointStatus[pointi] = 1;
// }
//}
label nBafflePoints = 0;
forAll(pointStatus, pointi)
{
if (pointStatus[pointi] != -1)
{
nBafflePoints++;
}
}
reduce(nBafflePoints, sumOp<label>());
label nPointAttract = 0;
label nEdgeAttract = 0;
forAll(pointStatus, pointi)
{
const point& pt = pp.localPoints()[pointi];
if (pointStatus[pointi] == 0) // baffle edge
{
// 1: attract to near feature edge first
Tuple2<label, pointIndexHit> nearInfo = findNearFeatureEdge
(
false, // isRegionPoint?
pp,
snapDist,
pointi,
pt,
edgeAttractors,
edgeConstraints,
patchAttraction,
patchConstraints
);
//- MEJ:
// 2: optionally override with nearest feature point.
// On baffles we don't have enough normals to construct a feature
// point so assume all feature edges are close to feature points
if (nearInfo.second().hit())
{
nEdgeAttract++;
if (baffleFeaturePoints)
{
nearInfo = findNearFeaturePoint
(
false, // isRegionPoint,
pp,
snapDist,
pointi,
pt, // estimatedPt,
// Feature-point to pp point
pointAttractor,
pointConstraints,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// pp point to nearest feature
patchAttraction,
patchConstraints
);
if (nearInfo.first() != -1)
{
nEdgeAttract--;
nPointAttract++;
}
}
}
}
else if (pointStatus[pointi] == 1) // baffle point
{
labelList nearFeat;
List<pointIndexHit> nearInfo;
features.findNearestPoint
(
pointField(1, pt),
scalarField(1, sqr(snapDist[pointi])),
nearFeat,
nearInfo
);
label feati = nearFeat[0];
if (feati != -1)
{
nPointAttract++;
label featPointi = nearInfo[0].index();
const point& featPt = nearInfo[0].hitPoint();
scalar distSqr = magSqr(featPt-pt);
// Check if already attracted
label oldPointi = pointAttractor[feati][featPointi];
if
(
oldPointi == -1
|| (
distSqr
< magSqr(featPt-pp.localPoints()[oldPointi])
)
)
{
pointAttractor[feati][featPointi] = pointi;
pointConstraints[feati][featPointi].first() = 3;
pointConstraints[feati][featPointi].second() = Zero;
// Store for later use
patchAttraction[pointi] = featPt-pt;
patchConstraints[pointi] =
pointConstraints[feati][featPointi];
if (oldPointi != -1)
{
// The current point is closer so wins. Reset
// the old point to attract to nearest edge
// instead.
findNearFeatureEdge
(
false, // isRegionPoint
pp,
snapDist,
oldPointi,
pp.localPoints()[oldPointi],
edgeAttractors,
edgeConstraints,
patchAttraction,
patchConstraints
);
}
}
else
{
// Make it fall through to check below
feati = -1;
}
}
// Not found a feature point or another point is already
// closer to that feature
if (feati == -1)
{
//Pout<< "*** Falling back to finding nearest feature"
// << " edge"
// << " for baffle-feature-point " << pt
// << endl;
Tuple2<label, pointIndexHit> nearInfo = findNearFeatureEdge
(
false, // isRegionPoint
pp,
snapDist,
pointi,
pt, // starting point
edgeAttractors,
edgeConstraints,
patchAttraction,
patchConstraints
);
if (nearInfo.first() != -1)
{
nEdgeAttract++;
}
}
}
}
reduce(nPointAttract, sumOp<label>());
reduce(nEdgeAttract, sumOp<label>());
Info<< "Baffle points : " << nBafflePoints
<< " of which attracted to :" << nl
<< " feature point : " << nPointAttract << nl
<< " feature edge : " << nEdgeAttract << nl
<< " rest : " << nBafflePoints-nPointAttract-nEdgeAttract
<< nl
<< endl;
}
void Foam::snappySnapDriver::reverseAttractMeshPoints
(
const label iter,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
// Feature-point to pp point
const List<labelList>& pointAttractor,
const List<List<pointConstraint>>& pointConstraints,
// Feature-edge to pp point
const List<List<DynamicList<point>>>& edgeAttractors,
const List<List<DynamicList<pointConstraint>>>& edgeConstraints,
const vectorField& rawPatchAttraction,
const List<pointConstraint>& rawPatchConstraints,
// pp point to nearest feature
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
const refinementFeatures& features = meshRefiner_.features();
// Find nearest mesh point to feature edge
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Reverse lookup : go through all edgeAttractors and find the
// nearest point on pp
// Get search domain and extend it a bit
treeBoundBox bb(pp.localPoints());
{
// Random number generator. Bit dodgy since not exactly random ;-)
Random rndGen(65431);
// Slightly extended bb. Slightly off-centred just so on symmetric
// geometry there are less face/edge aligned items.
bb = bb.extend(rndGen, 1e-4);
bb.min() -= point(ROOTVSMALL, ROOTVSMALL, ROOTVSMALL);
bb.max() += point(ROOTVSMALL, ROOTVSMALL, ROOTVSMALL);
}
// Collect candidate points for attraction
DynamicList<label> attractPoints(pp.nPoints());
{
const fvMesh& mesh = meshRefiner_.mesh();
boolList isFeatureEdgeOrPoint(pp.nPoints(), false);
label nFeats = 0;
forAll(rawPatchConstraints, pointi)
{
if (rawPatchConstraints[pointi].first() >= 2)
{
isFeatureEdgeOrPoint[pointi] = true;
nFeats++;
}
}
Info<< "Initially selected " << returnReduce(nFeats, sumOp<label>())
<< " mesh points out of "
<< returnReduce(pp.nPoints(), sumOp<label>())
<< " for reverse attraction." << endl;
// Make sure is synchronised (note: check if constraint is already
// synced in which case this is not needed here)
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
isFeatureEdgeOrPoint,
orEqOp<bool>(), // combine op
false
);
for (label nGrow = 0; nGrow < 1; nGrow++)
{
boolList newIsFeatureEdgeOrPoint(isFeatureEdgeOrPoint);
forAll(pp.localFaces(), facei)
{
const face& f = pp.localFaces()[facei];
forAll(f, fp)
{
if (isFeatureEdgeOrPoint[f[fp]])
{
// Mark all points on face
forAll(f, fp)
{
newIsFeatureEdgeOrPoint[f[fp]] = true;
}
break;
}
}
}
isFeatureEdgeOrPoint = newIsFeatureEdgeOrPoint;
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
isFeatureEdgeOrPoint,
orEqOp<bool>(), // combine op
false
);
}
// Collect attractPoints
forAll(isFeatureEdgeOrPoint, pointi)
{
if (isFeatureEdgeOrPoint[pointi])
{
attractPoints.append(pointi);
}
}
Info<< "Selected "
<< returnReduce(attractPoints.size(), sumOp<label>())
<< " mesh points out of "
<< returnReduce(pp.nPoints(), sumOp<label>())
<< " for reverse attraction." << endl;
}
indexedOctree<treeDataPoint> ppTree
(
treeDataPoint(pp.localPoints(), attractPoints),
bb, // overall search domain
8, // maxLevel
10, // leafsize
3.0 // duplicity
);
// Per mesh point the point on nearest feature edge.
patchAttraction.setSize(pp.nPoints());
patchAttraction = Zero;
patchConstraints.setSize(pp.nPoints());
patchConstraints = pointConstraint();
forAll(edgeAttractors, feati)
{
const List<DynamicList<point>>& edgeAttr = edgeAttractors[feati];
const List<DynamicList<pointConstraint>>& edgeConstr =
edgeConstraints[feati];
forAll(edgeAttr, featEdgei)
{
const DynamicList<point>& attr = edgeAttr[featEdgei];
forAll(attr, i)
{
// Find nearest pp point
const point& featPt = attr[i];
pointIndexHit nearInfo = ppTree.findNearest
(
featPt,
sqr(GREAT)
);
if (nearInfo.hit())
{
label pointi =
ppTree.shapes().pointLabels()[nearInfo.index()];
const point attraction = featPt-pp.localPoints()[pointi];
// Check if this point is already being attracted. If so
// override it only if nearer.
if
(
patchConstraints[pointi].first() <= 1
|| magSqr(attraction) < magSqr(patchAttraction[pointi])
)
{
patchAttraction[pointi] = attraction;
patchConstraints[pointi] = edgeConstr[featEdgei][i];
}
}
else
{
WarningInFunction
<< "Did not find pp point near " << featPt
<< endl;
}
}
}
}
// Different procs might have different patchAttraction,patchConstraints
// however these only contain geometric information, no topology
// so as long as we synchronise after overriding with feature points
// there is no problem, just possibly a small error.
// Find nearest mesh point to feature point
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// (overrides attraction to feature edge)
forAll(pointAttractor, feati)
{
const labelList& pointAttr = pointAttractor[feati];
const List<pointConstraint>& pointConstr = pointConstraints[feati];
forAll(pointAttr, featPointi)
{
if (pointAttr[featPointi] != -1)
{
const point& featPt = features[feati].points()
[
featPointi
];
// Find nearest pp point
pointIndexHit nearInfo = ppTree.findNearest
(
featPt,
sqr(GREAT)
);
if (nearInfo.hit())
{
label pointi =
ppTree.shapes().pointLabels()[nearInfo.index()];
const point& pt = pp.localPoints()[pointi];
const point attraction = featPt-pt;
// - already attracted to feature edge : point always wins
// - already attracted to feature point: nearest wins
if (patchConstraints[pointi].first() <= 1)
{
patchAttraction[pointi] = attraction;
patchConstraints[pointi] = pointConstr[featPointi];
}
else if (patchConstraints[pointi].first() == 2)
{
patchAttraction[pointi] = attraction;
patchConstraints[pointi] = pointConstr[featPointi];
}
else if (patchConstraints[pointi].first() == 3)
{
// Only if nearer
if
(
magSqr(attraction)
< magSqr(patchAttraction[pointi])
)
{
patchAttraction[pointi] = attraction;
patchConstraints[pointi] =
pointConstr[featPointi];
}
}
}
}
}
}
}
void Foam::snappySnapDriver::featureAttractionUsingFeatureEdges
(
const label iter,
const bool multiRegionFeatureSnap,
const bool detectBaffles,
const bool baffleFeaturePoints,
const bool releasePoints,
const bool stringFeatures,
const bool avoidDiagonal,
const scalar featureCos,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const vectorField& nearestDisp,
const vectorField& nearestNormal,
const List<List<point>>& pointFaceSurfNormals,
const List<List<point>>& pointFaceDisp,
const List<List<point>>& pointFaceCentres,
const labelListList& pointFacePatchID,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
const refinementFeatures& features = meshRefiner_.features();
const fvMesh& mesh = meshRefiner_.mesh();
const PackedBoolList isPatchMasterPoint
(
meshRefinement::getMasterPoints
(
mesh,
pp.meshPoints()
)
);
// Collect ordered attractions on feature edges
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Per feature, per feature-edge a list of attraction points and their
// originating vertex.
List<List<DynamicList<point>>> edgeAttractors(features.size());
List<List<DynamicList<pointConstraint>>> edgeConstraints
(
features.size()
);
forAll(features, feati)
{
label nFeatEdges = features[feati].edges().size();
edgeAttractors[feati].setSize(nFeatEdges);
edgeConstraints[feati].setSize(nFeatEdges);
}
// Per feature, per feature-point the pp point that is attracted to it.
// This list is only used to subset the feature-points that are actually
// used.
List<labelList> pointAttractor(features.size());
List<List<pointConstraint>> pointConstraints(features.size());
forAll(features, feati)
{
label nFeatPoints = features[feati].points().size();
pointAttractor[feati].setSize(nFeatPoints, -1);
pointConstraints[feati].setSize(nFeatPoints);
}
// Reverse: from pp point to nearest feature
vectorField rawPatchAttraction(pp.nPoints(), Zero);
List<pointConstraint> rawPatchConstraints(pp.nPoints());
determineFeatures
(
iter,
featureCos,
multiRegionFeatureSnap,
pp,
snapDist, // per point max distance and nearest surface
nearestDisp,
pointFaceSurfNormals, // per face nearest surface
pointFaceDisp,
pointFaceCentres,
pointFacePatchID,
// Feature-point to pp point
pointAttractor,
pointConstraints,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// pp point to nearest feature
rawPatchAttraction,
rawPatchConstraints
);
// Print a bit about the attraction from patch point to feature
if (debug)
{
Info<< "Raw geometric feature analysis : ";
writeStats(pp, isPatchMasterPoint, rawPatchConstraints);
}
// Baffle handling
// ~~~~~~~~~~~~~~~
// Override pointAttractor, edgeAttractor, rawPatchAttration etc. to
// implement 'baffle' handling.
// Baffle: the mesh pp point originates from a loose standing
// baffle.
// Sampling the surface with the surrounding face-centres only picks up
// a single triangle normal so above determineFeatures will not have
// detected anything. So explicitly pick up feature edges on the pp
// (after duplicating points & smoothing so will already have been
// expanded) and match these to the features.
if (detectBaffles)
{
determineBaffleFeatures
(
iter,
baffleFeaturePoints,
featureCos,
pp,
snapDist,
// Feature-point to pp point
pointAttractor,
pointConstraints,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// pp point to nearest feature
rawPatchAttraction,
rawPatchConstraints
);
}
// Print a bit about the attraction from patch point to feature
if (debug)
{
Info<< "After baffle feature analysis : ";
writeStats(pp, isPatchMasterPoint, rawPatchConstraints);
}
// Reverse lookup: Find nearest mesh point to feature edge
// ~~~~~~~~~~~~~~~~----------------~~~~~~~~~~~~~~~~~~~~~~~
// go through all edgeAttractors and find the nearest point on pp
reverseAttractMeshPoints
(
iter,
pp,
snapDist,
// Feature-point to pp point
pointAttractor,
pointConstraints,
// Feature-edge to pp point
edgeAttractors,
edgeConstraints,
// Estimated feature point
rawPatchAttraction,
rawPatchConstraints,
// pp point to nearest feature
patchAttraction,
patchConstraints
);
// Print a bit about the attraction from patch point to feature
if (debug)
{
Info<< "Reverse attract feature analysis : ";
writeStats(pp, isPatchMasterPoint, patchConstraints);
}
// Dump
if (debug&meshRefinement::ATTRACTION)
{
OBJstream featureEdgeStr
(
meshRefiner_.mesh().time().path()
/ "edgeAttractors_" + name(iter) + ".obj"
);
Info<< "Dumping feature-edge attraction to "
<< featureEdgeStr.name() << endl;
OBJstream featurePointStr
(
meshRefiner_.mesh().time().path()
/ "pointAttractors_" + name(iter) + ".obj"
);
Info<< "Dumping feature-point attraction to "
<< featurePointStr.name() << endl;
forAll(patchConstraints, pointi)
{
const point& pt = pp.localPoints()[pointi];
const vector& attr = patchAttraction[pointi];
if (patchConstraints[pointi].first() == 2)
{
featureEdgeStr.write(linePointRef(pt, pt+attr));
}
else if (patchConstraints[pointi].first() == 3)
{
featurePointStr.write(linePointRef(pt, pt+attr));
}
}
}
//MEJ: any faces that have multi-patch points only keep the
// multi-patch
// points. The other points on the face will be dragged along
// (hopefully)
if (releasePoints)
{
releasePointsNextToMultiPatch
(
iter,
featureCos,
pp,
snapDist,
pointFaceCentres,
pointFacePatchID,
rawPatchAttraction,
rawPatchConstraints,
patchAttraction,
patchConstraints
);
}
// Snap edges to feature edges
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Walk existing edges and snap remaining ones (that are marked as
// feature edges in rawPatchConstraints)
if (stringFeatures)
{
stringFeatureEdges
(
iter,
featureCos,
pp,
snapDist,
rawPatchAttraction,
rawPatchConstraints,
patchAttraction,
patchConstraints
);
}
// Avoid diagonal attraction
// ~~~~~~~~~~~~~~~~~~~~~~~~~
// Attract one of the non-diagonal points.
if (avoidDiagonal)
{
avoidDiagonalAttraction
(
iter,
featureCos,
pp,
patchAttraction,
patchConstraints
);
}
if (debug&meshRefinement::ATTRACTION)
{
dumpMove
(
meshRefiner_.mesh().time().path()
/ "patchAttraction_" + name(iter) + ".obj",
pp.localPoints(),
pp.localPoints() + patchAttraction
);
}
}
// Correct for squeezing of face
void Foam::snappySnapDriver::preventFaceSqueeze
(
const label iter,
const scalar featureCos,
const indirectPrimitivePatch& pp,
const scalarField& snapDist,
const vectorField& nearestAttraction,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints
) const
{
autoPtr<OBJstream> strPtr;
if (debug&meshRefinement::ATTRACTION)
{
strPtr.reset
(
new OBJstream
(
meshRefiner_.mesh().time().path()
/ "faceSqueeze_" + name(iter) + ".obj"
)
);
Info<< "Dumping faceSqueeze corrections to "
<< strPtr().name() << endl;
}
pointField points;
face singleF;
forAll(pp.localFaces(), facei)
{
const face& f = pp.localFaces()[facei];
if (f.size() != points.size())
{
points.setSize(f.size());
singleF.setSize(f.size());
for (label i = 0; i < f.size(); i++)
{
singleF[i] = i;
}
}
label nConstraints = 0;
forAll(f, fp)
{
label pointi = f[fp];
const point& pt = pp.localPoints()[pointi];
if (patchConstraints[pointi].first() > 1)
{
points[fp] = pt + patchAttraction[pointi];
nConstraints++;
}
else
{
points[fp] = pt;
}
}
if (nConstraints == f.size())
{
if (f.size() == 3)
{
// Triangle: knock out attraction altogether
// For now keep the points on the longest edge
label maxFp = -1;
scalar maxS = -1;
forAll(f, fp)
{
const point& pt = pp.localPoints()[f[fp]];
const point& nextPt = pp.localPoints()[f.nextLabel(fp)];
scalar s = magSqr(pt-nextPt);
if (s > maxS)
{
maxS = s;
maxFp = fp;
}
}
if (maxFp != -1)
{
label pointi = f.prevLabel(maxFp);
// Reset attraction on pointi to nearest
const point& pt = pp.localPoints()[pointi];
//Pout<< "** on triangle " << pp.faceCentres()[facei]
// << " knocking out attraction to " << pointi
// << " at:" << pt
// << endl;
patchAttraction[pointi] = nearestAttraction[pointi];
if (strPtr.valid())
{
strPtr().write
(
linePointRef(pt, pt+patchAttraction[pointi])
);
}
}
}
else
{
scalar oldArea = f.mag(pp.localPoints());
scalar newArea = singleF.mag(points);
if (newArea < 0.1*oldArea)
{
// For now remove the point with largest distance
label maxFp = -1;
scalar maxS = -1;
forAll(f, fp)
{
scalar s = magSqr(patchAttraction[f[fp]]);
if (s > maxS)
{
maxS = s;
maxFp = fp;
}
}
if (maxFp != -1)
{
label pointi = f[maxFp];
// Lower attraction on pointi
patchAttraction[pointi] *= 0.5;
}
}
}
}
}
}
Foam::vectorField Foam::snappySnapDriver::calcNearestSurfaceFeature
(
const snapParameters& snapParams,
const bool alignMeshEdges,
const label iter,
const scalar featureCos,
const scalar featureAttract,
const scalarField& snapDist,
const vectorField& nearestDisp,
const vectorField& nearestNormal,
motionSmoother& meshMover,
vectorField& patchAttraction,
List<pointConstraint>& patchConstraints,
DynamicList<label>& splitFaces,
DynamicList<labelPair>& splits
) const
{
const Switch implicitFeatureAttraction = snapParams.implicitFeatureSnap();
const Switch explicitFeatureAttraction = snapParams.explicitFeatureSnap();
const Switch multiRegionFeatureSnap = snapParams.multiRegionFeatureSnap();
Info<< "Overriding displacement on features :" << nl
<< " implicit features : " << implicitFeatureAttraction << nl
<< " explicit features : " << explicitFeatureAttraction << nl
<< " multi-patch features : " << multiRegionFeatureSnap << nl
<< endl;
const indirectPrimitivePatch& pp = meshMover.patch();
const pointField& localPoints = pp.localPoints();
const fvMesh& mesh = meshRefiner_.mesh();
//const PackedBoolList isMasterPoint(syncTools::getMasterPoints(mesh));
const PackedBoolList isPatchMasterPoint
(
meshRefinement::getMasterPoints
(
mesh,
pp.meshPoints()
)
);
// Per point, per surrounding face:
// - faceSurfaceNormal
// - faceDisp
// - faceCentres
List<List<point>> pointFaceSurfNormals;
List<List<point>> pointFaceDisp;
List<List<point>> pointFaceCentres;
List<labelList> pointFacePatchID;
{
// Calculate attraction distance per face (from the attraction distance
// per point)
scalarField faceSnapDist(pp.size(), -GREAT);
forAll(pp.localFaces(), facei)
{
const face& f = pp.localFaces()[facei];
forAll(f, fp)
{
faceSnapDist[facei] = max(faceSnapDist[facei], snapDist[f[fp]]);
}
}
// Displacement and orientation per pp face
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// vector from point on surface back to face centre
vectorField faceDisp(pp.size(), Zero);
// normal of surface at point on surface
vectorField faceSurfaceNormal(pp.size(), Zero);
labelList faceSurfaceGlobalRegion(pp.size(), -1);
//vectorField faceRotation(pp.size(), Zero);
calcNearestFace
(
iter,
pp,
faceSnapDist,
faceDisp,
faceSurfaceNormal,
faceSurfaceGlobalRegion
//faceRotation
);
// Collect (possibly remote) per point data of all surrounding faces
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// - faceSurfaceNormal
// - faceDisp
// - faceCentres
calcNearestFacePointProperties
(
iter,
pp,
faceDisp,
faceSurfaceNormal,
faceSurfaceGlobalRegion,
pointFaceSurfNormals,
pointFaceDisp,
pointFaceCentres,
pointFacePatchID
);
}
// Start off with nearest point on surface
vectorField patchDisp = nearestDisp;
// Main calculation
// ~~~~~~~~~~~~~~~~
// This is the main intelligence which calculates per point the vector to
// attract it to the nearest surface. There are lots of possibilities
// here.
// Nearest feature
patchAttraction.setSize(localPoints.size());
patchAttraction = Zero;
// Constraints at feature
patchConstraints.setSize(localPoints.size());
patchConstraints = pointConstraint();
if (implicitFeatureAttraction)
{
// Sample faces around each point and see if nearest surface normal
// differs. Reconstruct a feature edge/point if possible and snap to
// it.
featureAttractionUsingReconstruction
(
iter,
featureCos,
pp,
snapDist,
nearestDisp,
pointFaceSurfNormals,
pointFaceDisp,
pointFaceCentres,
pointFacePatchID,
patchAttraction,
patchConstraints
);
}
if (explicitFeatureAttraction)
{
// Only do fancy stuff if alignMeshEdges
bool releasePoints = false;
bool stringFeatures = false;
bool avoidDiagonal = false;
if (alignMeshEdges)
{
releasePoints = snapParams.releasePoints();
stringFeatures = snapParams.stringFeatures();
avoidDiagonal = snapParams.avoidDiagonal();
}
// Sample faces around each point and see if nearest surface normal
// differs. For those find the nearest real feature edge/point and
// store the correspondence. Then loop over feature edge/point
// and attract those nearest mesh point. (the first phase just is
// a subsetting of candidate points, the second makes sure that only
// one mesh point gets attracted per feature)
featureAttractionUsingFeatureEdges
(
iter,
multiRegionFeatureSnap,
snapParams.detectBaffles(),
snapParams.baffleFeaturePoints(), // all points on baffle edges
// are attracted to feature pts
releasePoints,
stringFeatures,
avoidDiagonal,
featureCos,
pp,
snapDist,
nearestDisp,
nearestNormal,
pointFaceSurfNormals,
pointFaceDisp,
pointFaceCentres,
pointFacePatchID,
patchAttraction,
patchConstraints
);
}
if (!alignMeshEdges)
{
const scalar concaveCos = Foam::cos
(
degToRad(snapParams.concaveAngle())
);
const scalar minAreaRatio = snapParams.minAreaRatio();
Info<< "Experimental: introducing face splits to avoid rotating"
<< " mesh edges. Splitting faces when" << nl
<< indent << "- angle not concave by more than "
<< snapParams.concaveAngle() << " degrees" << nl
<< indent << "- resulting triangles of similar area "
<< " (ratio within " << minAreaRatio << ")" << nl
<< endl;
splitDiagonals
(
featureCos,
concaveCos,
minAreaRatio,
pp,
nearestDisp,
nearestNormal,
patchAttraction,
patchConstraints,
splitFaces,
splits
);
if (debug)
{
Info<< "Diagonal attraction feature correction : ";
writeStats(pp, isPatchMasterPoint, patchConstraints);
}
}
preventFaceSqueeze
(
iter,
featureCos,
pp,
snapDist,
nearestDisp,
patchAttraction,
patchConstraints
);
{
vector avgPatchDisp = meshRefinement::gAverage
(
isPatchMasterPoint,
patchDisp
);
vector avgPatchAttr = meshRefinement::gAverage
(
isPatchMasterPoint,
patchAttraction
);
Info<< "Attraction:" << endl
<< " linear : max:" << gMaxMagSqr(patchDisp)
<< " avg:" << avgPatchDisp << endl
<< " feature : max:" << gMaxMagSqr(patchAttraction)
<< " avg:" << avgPatchAttr << endl;
}
// So now we have:
// - patchDisp : point movement to go to nearest point on surface
// (either direct or through interpolation of
// face nearest)
// - patchAttraction : direct attraction to features
// - patchConstraints : type of features
// Use any combination of patchDisp and direct feature attraction.
// Mix with direct feature attraction
forAll(patchConstraints, pointi)
{
if (patchConstraints[pointi].first() > 1)
{
patchDisp[pointi] =
(1.0-featureAttract)*patchDisp[pointi]
+ featureAttract*patchAttraction[pointi];
}
}
// Count
{
Info<< "Feature analysis : ";
writeStats(pp, isPatchMasterPoint, patchConstraints);
}
// Now we have the displacement per patch point to move onto the surface
// Split into tangential and normal direction.
// - start off with all non-constrained points following the constrained
// ones since point normals not relevant.
// - finish with only tangential component smoothed.
// Note: tangential is most
// likely to come purely from face-centre snapping, not face rotation.
// Note: could use the constraints here (constraintTransformation())
// but this is not necessarily accurate and we're smoothing to
// get out of problems.
if (featureAttract < 1-0.001)
{
//const PackedBoolList isMasterEdge(syncTools::getMasterEdges(mesh));
const labelList meshEdges
(
pp.meshEdges(mesh.edges(), mesh.pointEdges())
);
const PackedBoolList isPatchMasterEdge
(
meshRefinement::getMasterEdges
(
mesh,
meshEdges
)
);
const vectorField pointNormals
(
PatchTools::pointNormals
(
mesh,
pp
)
);
// 1. Smoothed all displacement
vectorField smoothedPatchDisp = patchDisp;
smoothAndConstrain
(
isPatchMasterEdge,
pp,
meshEdges,
patchConstraints,
smoothedPatchDisp
);
// 2. Smoothed tangential component
vectorField tangPatchDisp = patchDisp;
tangPatchDisp -= (pointNormals & patchDisp) * pointNormals;
smoothAndConstrain
(
isPatchMasterEdge,
pp,
meshEdges,
patchConstraints,
tangPatchDisp
);
// Re-add normal component
tangPatchDisp += (pointNormals & patchDisp) * pointNormals;
if (debug&meshRefinement::ATTRACTION)
{
dumpMove
(
mesh.time().path()
/ "tangPatchDispConstrained_" + name(iter) + ".obj",
pp.localPoints(),
pp.localPoints() + tangPatchDisp
);
}
patchDisp =
(1.0-featureAttract)*smoothedPatchDisp
+ featureAttract*tangPatchDisp;
}
const scalar relax = featureAttract;
patchDisp *= relax;
// Points on zones in one domain but only present as point on other
// will not do condition 2 on all. Sync explicitly.
syncTools::syncPointList
(
mesh,
pp.meshPoints(),
patchDisp,
minMagSqrEqOp<point>(), // combine op
vector(GREAT, GREAT, GREAT) // null value (note: cant use VGREAT)
);
return patchDisp;
}
// ************************************************************************* //