distanceSurfaceFilter.C
Go to the documentation of this file.
1/*---------------------------------------------------------------------------*\
2 ========= |
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4 \\ / O peration |
5 \\ / A nd | www.openfoam.com
6 \\/ M anipulation |
7-------------------------------------------------------------------------------
8 Copyright (C) 2020-2021 OpenCFD Ltd.
9-------------------------------------------------------------------------------
10License
11 This file is part of OpenFOAM.
12
13 OpenFOAM is free software: you can redistribute it and/or modify it
14 under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25
26\*---------------------------------------------------------------------------*/
27
28#include "distanceSurface.H"
29#include "regionSplit.H"
30#include "syncTools.H"
31#include "ListOps.H"
32#include "vtkSurfaceWriter.H"
33
34// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
35
37(
38 bitSet& ignoreCells,
39 const isoSurfaceBase& isoCutter
40) const
41{
42 // With the cell/point distance fields we can take a second pass at
43 // pre-filtering.
44 // This duplicates how cut detection is determined in the cell/topo
45 // algorithms but is fairly inexpensive (creates no geometry)
46
47 bool changed = false;
48
49 for (label celli = 0; celli < mesh_.nCells(); ++celli)
50 {
51 if (ignoreCells.test(celli))
52 {
53 continue;
54 }
55
56 auto cut = isoCutter.getCellCutType(celli);
58 {
59 ignoreCells.set(celli);
60 changed = true;
61 }
62 }
63
64 return changed;
65}
66
67
69(
70 const bitSet& ignoreCells
71) const
72{
73 // Prepare for region split
74
75 bitSet blockedFaces(mesh_.nFaces());
76
77 const labelList& faceOwn = mesh_.faceOwner();
78 const labelList& faceNei = mesh_.faceNeighbour();
79
80 // Could be more efficient
81 for (label facei = 0; facei < mesh_.nInternalFaces(); ++facei)
82 {
83 // If only one cell is blocked, the face corresponds
84 // to an exposed subMesh face
85
86 if
87 (
88 ignoreCells.test(faceOwn[facei])
89 != ignoreCells.test(faceNei[facei])
90 )
91 {
92 blockedFaces.set(facei);
93 }
94 }
95
96 for (const polyPatch& patch : mesh_.boundaryMesh())
97 {
98 if (!patch.coupled())
99 {
100 continue;
101 }
102
103 forAll(patch, patchFacei)
104 {
105 const label facei = patch.start() + patchFacei;
106 if (ignoreCells.test(faceOwn[facei]))
107 {
108 blockedFaces.set(facei);
109 }
110 }
111 }
112
113 syncTools::syncFaceList(mesh_, blockedFaces, xorEqOp<unsigned int>());
114
115 return blockedFaces;
116}
117
118
120(
121 bitSet& ignoreCells
122) const
123{
124 // For region split
125 bitSet blockedFaces(filterPrepareRegionSplit(ignoreCells));
126
127 // Split region
128 regionSplit rs(mesh_, blockedFaces);
129 blockedFaces.clearStorage();
130
131 const labelList& regionColour = rs;
132
133 // Identical number of regions on all processors
134 labelList nCutsPerRegion(rs.nRegions(), Zero);
135
136 // Count cut cells (ie, unblocked)
137 forAll(regionColour, celli)
138 {
139 if (!ignoreCells.test(celli))
140 {
141 ++nCutsPerRegion[regionColour[celli]];
142 }
143 }
144
145 // Sum totals from all processors
147
148
149 // Define which regions to keep
150 boolList keepRegion(rs.nRegions(), false);
151
152 if (Pstream::master())
153 {
154 const label largest = findMax(nCutsPerRegion);
155
156 if (largest == -1)
157 {
158 // Should not happen
159 keepRegion = true;
160 }
161 else
162 {
163 keepRegion[largest] = true;
164 }
165
166 if (debug)
167 {
168 Info<< "Had " << sum(nCutsPerRegion) << " cuts, in "
169 << nCutsPerRegion.size() << " regions, largest is "
170 << largest << ": " << flatOutput(nCutsPerRegion) << nl;
171 }
172 }
173
174 Pstream::broadcast(keepRegion);
175
176 forAll(regionColour, celli)
177 {
178 if (!keepRegion.test(regionColour[celli]))
179 {
180 ignoreCells.set(celli);
181 }
182 }
183}
184
185
187(
188 bitSet& ignoreCells
189) const
190{
191 if (nearestPoints_.empty())
192 {
194 << "Ignoring nearestPoints - no points provided" << nl
195 << endl;
196 return;
197 }
198
199 // For region split
200 bitSet blockedFaces(filterPrepareRegionSplit(ignoreCells));
201
202 // Split region
203 regionSplit rs(mesh_, blockedFaces);
204 blockedFaces.clearStorage();
205
206 const labelList& regionColour = rs;
207
208 const pointField& cc = mesh_.cellCentres();
209 const pointField& nearPts = nearestPoints_;
210
211 // The magSqr distance and region
212 typedef Tuple2<scalar, label> nearInfo;
213 List<nearInfo> nearest(nearPts.size(), nearInfo(GREAT, -1));
214
215 // Also collect cuts per region, may be useful for rejecting
216 // small regions. Code as per filterKeepLargestRegion
217 labelList nCutsPerRegion(rs.nRegions(), Zero);
218
219 forAll(cc, celli)
220 {
221 if (ignoreCells.test(celli))
222 {
223 continue;
224 }
225
226 const point& pt = cc[celli];
227 const label regioni = regionColour[celli];
228
229 ++nCutsPerRegion[regioni];
230
231 label pointi = 0;
232 for (nearInfo& near : nearest)
233 {
234 const scalar distSqr = magSqr(nearPts[pointi] - pt);
235 ++pointi;
236
237 if (distSqr < near.first())
238 {
239 near.first() = distSqr;
240 near.second() = regioni;
241 }
242 }
243 }
244
245 // Sum totals from all processors
247
248 // Get nearest
250
251
252 // Define which regions to keep
253
254 boolList keepRegion(rs.nRegions(), false);
255
256 if (Pstream::master())
257 {
258 const label largest = findMax(nCutsPerRegion);
259
260 for (const nearInfo& near : nearest)
261 {
262 const scalar distSqr = near.first();
263 const label regioni = near.second();
264
265 if (regioni != -1 && distSqr < maxDistanceSqr_)
266 {
267 keepRegion[regioni] = true;
268 }
269 }
270
271 if (debug)
272 {
273 Info<< "Had " << sum(nCutsPerRegion) << " cuts, in "
274 << nCutsPerRegion.size() << " regions, largest is "
275 << largest << ": " << flatOutput(nCutsPerRegion) << nl;
276
277 Info<< "nearestPoints (max distance = "
278 << sqrt(maxDistanceSqr_) << ")" << nl;
279
280 forAll(nearPts, pointi)
281 {
282 const scalar dist = sqrt(nearest[pointi].first());
283 const label regioni = nearest[pointi].second();
284
285 Info<< " " << nearPts[pointi] << " region "
286 << regioni << " distance "
287 << dist;
288
289 if (!keepRegion.test(regioni))
290 {
291 Info<< " too far";
292 }
293 Info<< nl;
294 }
295 }
296 }
297
298 Pstream::broadcast(keepRegion);
299
300 forAll(regionColour, celli)
301 {
302 if (!keepRegion.test(regionColour[celli]))
303 {
304 ignoreCells.set(celli);
305 }
306 }
307}
308
309
311(
312 bitSet& ignoreCells
313) const
314{
315 const searchableSurface& geom = geometryPtr_();
316
317 // For face distances
318 const pointField& fc = surface_.faceCentres();
319
320 // For region split
321 bitSet blockedFaces(filterPrepareRegionSplit(ignoreCells));
322
323 // Split region
324 regionSplit rs(mesh_, blockedFaces);
325 blockedFaces.clearStorage();
326
327 const labelList& regionColour = rs;
328
329 // For each face
330 scalarField faceDistance(fc.size(), GREAT);
331
332 {
333 List<pointIndexHit> nearest;
334 geom.findNearest
335 (
336 fc,
337 // Use initialized field (GREAT) to limit search too
338 faceDistance,
339 nearest
340 );
341 calcAbsoluteDistance(faceDistance, fc, nearest);
342 }
343
344 // Identical number of regions on all processors
345 scalarField areaRegion(rs.nRegions(), Zero);
346 scalarField distRegion(rs.nRegions(), Zero);
347
348 forAll(meshCells_, facei)
349 {
350 const label celli = meshCells_[facei];
351 const label regioni = regionColour[celli];
352
353 const scalar faceArea = surface_[facei].mag(surface_.points());
354 distRegion[regioni] += (faceDistance[facei] * faceArea);
355 areaRegion[regioni] += (faceArea);
356 }
357
360
361 if (Pstream::master())
362 {
363 forAll(distRegion, regioni)
364 {
365 distRegion[regioni] /= (areaRegion[regioni] + VSMALL);
366 }
367 }
368
369 Pstream::broadcast(distRegion);
370
371
372 // Define the per-face acceptance based on the region average distance
373
374 bitSet acceptFaces(fc.size());
375 bool prune(false);
376
377 forAll(meshCells_, facei)
378 {
379 const label celli = meshCells_[facei];
380 const label regioni = regionColour[celli];
381
382 // NB: do not filter by individual faces as well since this
383 // has been reported to cause minor holes for surfaces with
384 // high curvature! (2021-06-10)
385
386 if (absProximity_ < distRegion[regioni])
387 {
388 prune = true;
389 }
390 else
391 {
392 acceptFaces.set(facei);
393 }
394 }
395
396 // Heavier debugging
397 if (debug & 4)
398 {
399 const fileName outputName(surfaceName() + "-region-proximity-filter");
400
401 Info<< "Writing debug surface: " << outputName << nl;
402
404 (
405 surface_.points(),
406 surface_, // faces
408 );
409
410 writer.write("absolute-distance", faceDistance);
411
412 // Region segmentation
413 labelField faceRegion
414 (
415 ListOps::create<label>
416 (
417 meshCells_,
418 [&](const label celli){ return regionColour[celli]; }
419 )
420 );
421 writer.write("face-region", faceRegion);
422
423 // Region-wise filter state
424 labelField faceFilterState
425 (
426 ListOps::createWithValue<label>(surface_.size(), acceptFaces, 1, 0)
427 );
428 writer.write("filter-state", faceFilterState);
429 }
430
431
432 // If filtering with region proximity results in zero faces,
433 // revert to face-only proximity filter
434 if (returnReduce(acceptFaces.none(), andOp<bool>()))
435 {
436 acceptFaces.reset();
437 prune = false;
438
439 // Consider the absolute proximity of the face centres
440 forAll(faceDistance, facei)
441 {
442 if (absProximity_ < faceDistance[facei])
443 {
444 prune = true;
445 }
446 else
447 {
448 acceptFaces.set(facei);
449 }
450 }
451 }
452
453 if (prune)
454 {
455 labelList pointMap, faceMap;
456 meshedSurface filtered
457 (
458 surface_.subsetMesh(acceptFaces, pointMap, faceMap)
459 );
460 surface_.transfer(filtered);
461
462 meshCells_ = UIndirectList<label>(meshCells_, faceMap)();
463 }
464}
465
466
468{
469 const searchableSurface& geom = geometryPtr_();
470
471 // For face distances
472 const pointField& fc = surface_.faceCentres();
473
474 // For each face
475 scalarField faceDistance(fc.size(), GREAT);
476 scalarField faceNormalDistance; // Debugging only
477 {
478 List<pointIndexHit> nearest;
479 geom.findNearest
480 (
481 fc,
482 // Use initialized field (GREAT) to limit search too
483 faceDistance,
484 nearest
485 );
486 calcAbsoluteDistance(faceDistance, fc, nearest);
487
488 // Heavier debugging
489 if (debug & 4)
490 {
491 vectorField norms;
492 geom.getNormal(nearest, norms);
493
494 faceNormalDistance.resize(fc.size());
495
496 forAll(nearest, i)
497 {
498 const vector diff(fc[i] - nearest[i].point());
499
500 faceNormalDistance[i] = Foam::mag((diff & norms[i]));
501 }
502 }
503 }
504
505 // Note
506 // Proximity checks using the face points (nearest hit) to establish
507 // a length scale are too fragile. Can easily have stretched faces
508 // where the centre is less than say 0.3-0.5 of the centre-point distance
509 // but they are still outside.
510
511 // Using the absolute proximity of the face centres is more robust.
512
513 bitSet acceptFaces(fc.size());
514 bool prune(false);
515
516 // Consider the absolute proximity of the face centres
517 forAll(faceDistance, facei)
518 {
519 if (absProximity_ < faceDistance[facei])
520 {
521 prune = true;
522 if (debug & 2)
523 {
524 Pout<< "trim reject: "
525 << faceDistance[facei] << nl;
526 }
527 }
528 else
529 {
530 acceptFaces.set(facei);
531 }
532 }
533
534
535 // Heavier debugging
536 if (debug & 4)
537 {
538 const fileName outputName(surfaceName() + "-face-proximity-filter");
539
540 Info<< "Writing debug surface: " << outputName << nl;
541
543 (
544 surface_.points(),
545 surface_, // faces
547 );
548
549 writer.write("absolute-distance", faceDistance);
550 writer.write("normal-distance", faceNormalDistance);
551
552 // Region-wise filter state
553 labelField faceFilterState
554 (
555 ListOps::createWithValue<label>(surface_.size(), acceptFaces, 1, 0)
556 );
557 writer.write("filter-state", faceFilterState);
558 }
559
560
561 if (prune)
562 {
563 labelList pointMap, faceMap;
564 meshedSurface filtered
565 (
566 surface_.subsetMesh(acceptFaces, pointMap, faceMap)
567 );
568 surface_.transfer(filtered);
569
570 meshCells_ = UIndirectList<label>(meshCells_, faceMap)();
571 }
572}
573
574
575// ************************************************************************* //
Various functions to operate on Lists.
vtk::internalMeshWriter writer(topoMesh, topoCells, vtk::formatType::INLINE_ASCII, runTime.path()/"blockTopology")
void resize(const label len)
Adjust allocated size of list.
Definition: ListI.H:139
void clearStorage()
Clear the list and delete storage.
Definition: PackedListI.H:520
void reset()
Clear all bits but do not adjust the addressable size.
Definition: PackedListI.H:505
static void broadcast(Type &value, const label comm=UPstream::worldComm)
static void listCombineGather(const List< commsStruct > &comms, List< T > &values, const CombineOp &cop, const int tag, const label comm)
A 2-tuple for storing two objects of dissimilar types. The container is similar in purpose to std::pa...
Definition: Tuple2.H:58
A List with indirect addressing. Like IndirectList but does not store addressing.
Definition: IndirectList.H:79
std::enable_if< std::is_same< bool, TypeT >::value, bool >::type test(const label i) const
Definition: UList.H:518
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:114
A bitSet stores bits (elements with only two states) in packed internal format and supports a variety...
Definition: bitSet.H:66
void set(const bitSet &bitset)
Set specified bits from another bitset.
Definition: bitSetI.H:590
bool none() const
True if no bits in this bitset are set.
Definition: bitSetI.H:488
bool test(const label pos) const
Test value at specified position, never auto-vivify entries.
Definition: bitSetI.H:521
void filterFaceProximity()
Adjust extracted iso-surface to remove far faces.
bitSet filterPrepareRegionSplit(const bitSet &ignoreCells) const
Prepare blockedFaces for region split.
void filterRegionProximity(bitSet &ignoreCells) const
Remove region(s) that have far faces.
void filterKeepNearestRegions(bitSet &ignoreCells) const
Keep region(s) closest to the nearest points.
bool refineBlockedCells(bitSet &ignoreCells, const isoSurfaceBase &isoContext) const
Re-filter the blocked cells based on the anticipated cuts.
void filterKeepLargestRegion(bitSet &ignoreCells) const
Keep region with the most cuts (after region split)
A class for handling file names.
Definition: fileName.H:76
Low-level components common to various iso-surface algorithms.
@ ANYCUT
Any cut type (bitmask)
cutType getCellCutType(const label celli) const
A patch is a list of labels that address the faces in the global face list.
Definition: polyPatch.H:75
label nCells() const noexcept
Number of mesh cells.
This class separates the mesh into distinct unconnected regions, each of which is then given a label ...
Definition: regionSplit.H:144
label nRegions() const
Return total number of regions.
Definition: regionSplit.H:294
Patchify triangles based on orientation w.r.t other (triangulated or triangulatable) surfaces.
Base class of (analytical or triangulated) surface. Encapsulates all the search routines....
virtual void findNearest(const pointField &sample, const scalarField &nearestDistSqr, List< pointIndexHit > &) const =0
virtual void getNormal(const List< pointIndexHit > &, vectorField &normal) const =0
From a set of points and indices get the normal.
splitCell * master() const
Definition: splitCell.H:113
A surfaceWriter for VTK legacy (.vtk) or XML (.vtp) format.
static void syncFaceList(const polyMesh &mesh, UList< T > &faceValues, const CombineOp &cop)
Synchronize values on all mesh faces.
Definition: syncTools.H:396
word outputName("finiteArea-edges.obj")
#define WarningInFunction
Report a warning using Foam::Warning.
Pair< int > faceMap(const label facePi, const face &faceP, const label faceNi, const face &faceN)
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
messageStream Info
Information stream (stdout output on master, null elsewhere)
vector point
Point is a vector.
Definition: point.H:43
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:372
dimensionedScalar sqrt(const dimensionedScalar &ds)
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
scalar diff(const triad &A, const triad &B)
Return a quantity of the difference between two triads.
Definition: triad.C:378
FlatOutput::OutputAdaptor< Container, Delimiters > flatOutput(const Container &obj, Delimiters delim)
Global flatOutput() function with specified output delimiters.
Definition: FlatOutput.H:215
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:131
T returnReduce(const T &value, const BinaryOp &bop, const int tag=UPstream::msgType(), const label comm=UPstream::worldComm)
Reduce (copy) and return value.
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
label findMax(const ListType &input, label start=0)
dimensioned< typename typeOfMag< Type >::type > magSqr(const dimensioned< Type > &dt)
constexpr char nl
The newline '\n' character (0x0a)
Definition: Ostream.H:53
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:333
Assign tuple-like container to use the one with the smaller value of first.
Definition: Tuple2.H:250