mergePoints.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) 2011-2016 OpenFOAM Foundation
9 Copyright (C) 2017-2022 OpenCFD Ltd.
10-------------------------------------------------------------------------------
11License
12 This file is part of OpenFOAM.
13
14 OpenFOAM is free software: you can redistribute it and/or modify it
15 under the terms of the GNU General Public License as published by
16 the Free Software Foundation, either version 3 of the License, or
17 (at your option) any later version.
18
19 OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26
27\*---------------------------------------------------------------------------*/
28
29#include "ListOps.H" // sortedOrder, ListOps::identity
30
31// * * * * * * * * * * * * * * * Implementation * * * * * * * * * * * * * * //
32
33template<class PointList, class IndexerOp>
35(
36 const PointList& points,
37 const IndexerOp& indexer,
38 const label nSubPoints,
39 labelList& pointToUnique,
40 labelList& uniquePoints,
41 const scalar mergeTol,
42 const bool verbose
43)
44{
45 const label nTotPoints = points.size();
46
47 if (!nTotPoints || !nSubPoints)
48 {
49 // Nothing to do
50 pointToUnique = identity(nTotPoints);
51 uniquePoints = pointToUnique;
52 return 0; // No points removed
53 }
54
55 // Properly size for old to new mapping array
56 pointToUnique.resize_nocopy(nTotPoints);
57
58
59 // Use the boundBox minimum as the reference point. This
60 // stretches distances with fewer collisions than a mid-point
61 // reference would.
62
63 auto comparePoint(points[indexer(0)]);
64 for (label pointi = 1; pointi < nSubPoints; ++pointi)
65 {
66 comparePoint = min(comparePoint, points[indexer(pointi)]);
67 }
68
69 // We're comparing distance squared to reference point first.
70 // Say if starting from two close points:
71 // x, y, z
72 // x+mergeTol, y+mergeTol, z+mergeTol
73 // Then the magSqr of both will be
74 // x^2+y^2+z^2
75 // x^2+y^2+z^2 + 2*mergeTol*(x+z+y) + mergeTol^2*...
76 // so the difference will be 2*mergeTol*(x+y+z)
77
78 const scalar mergeTolSqr(magSqr(mergeTol));
79
80 // Use magSqr distance for the points sort order
81 List<scalar> sqrDist(nSubPoints);
82 for (label pointi = 0; pointi < nSubPoints; ++pointi)
83 {
84 const auto& p = points[indexer(pointi)];
85
86 sqrDist[pointi] =
87 (
88 // Use scalar precision
89 magSqr(scalar(p.x() - comparePoint.x()))
90 + magSqr(scalar(p.y() - comparePoint.y()))
91 + magSqr(scalar(p.z() - comparePoint.z()))
92 );
93 }
94 labelList order(Foam::sortedOrder(sqrDist));
95
96 List<scalar> sortedTol(nSubPoints);
97 forAll(order, sorti)
98 {
99 const auto& p = points[indexer(order[sorti])];
100
101 sortedTol[sorti] =
102 (
103 2*mergeTol*
104 (
105 // Use scalar precision
106 mag(scalar(p.x() - comparePoint.x()))
107 + mag(scalar(p.y() - comparePoint.y()))
108 + mag(scalar(p.z() - comparePoint.z()))
109 )
110 );
111 }
112
113
114 // Bookkeeping parameters
115 // ~~~~~~~~~~~~~~~~~~~~~~
116
117 // Will only be working on a subset of the points
118 // Can use a slice of pointToUnique (full length not needed until later).
119 SubList<label> subPointMap(pointToUnique, nSubPoints);
120
121 // Track number of unique points - this will form an offsets table
122 labelList newPointCounts(nSubPoints, Zero);
123
124 label nNewPoints = 0;
125 for (label sorti = 0; sorti < order.size(); ++sorti)
126 {
127 // The (sub)point index
128 const label pointi = order[sorti];
129 const scalar currDist = sqrDist[order[sorti]];
130 const auto& currPoint = points[indexer(pointi)];
131
132 // Compare to previous points to find equal one
133 // - automatically a no-op for sorti == 0 (the first point)
134
135 bool matched = false;
136
137 for
138 (
139 label prevSorti = sorti - 1;
140 (
141 prevSorti >= 0
142 && (mag(sqrDist[order[prevSorti]] - currDist) <= sortedTol[sorti])
143 );
144 --prevSorti
145 )
146 {
147 const label prevPointi = order[prevSorti];
148 const auto& prevPoint = points[indexer(prevPointi)];
149
150 // Matched within tolerance?
151 matched =
152 (
153 (
154 // Use scalar precision
155 magSqr(scalar(currPoint.x() - prevPoint.x()))
156 + magSqr(scalar(currPoint.y() - prevPoint.y()))
157 + magSqr(scalar(currPoint.z() - prevPoint.z()))
158 ) <= mergeTolSqr
159 );
160
161 if (matched)
162 {
163 // Both pointi and prevPointi have similar coordinates.
164 // Map to the same new point.
165 subPointMap[pointi] = subPointMap[prevPointi];
166
167 if (verbose)
168 {
169 Pout<< "Foam::mergePoints : [" << subPointMap[pointi]
170 << "] Point " << pointi << " duplicate of "
171 << prevPointi << " : coordinates:" << currPoint
172 << " and " << prevPoint << endl;
173 }
174 break;
175 }
176 }
177
178 if (!matched)
179 {
180 // Differs. Store new point.
181 subPointMap[pointi] = nNewPoints++;
182
189 }
190 ++newPointCounts[subPointMap[pointi]];
191 }
192
193 const label nDupPoints(nSubPoints - nNewPoints);
194 const label nUniqPoints(nTotPoints - nDupPoints);
195
196 if (verbose)
197 {
198 Pout<< "Foam::mergePoints : "
199 << "Merging removed " << nDupPoints << '/'
200 << nTotPoints << " points" << endl;
201 }
202
203 if (!nDupPoints)
204 {
205 // Nothing to do
206 pointToUnique = identity(nTotPoints);
207 uniquePoints = pointToUnique;
208 return 0; // No points removed
209 }
210
211
212 // The subPointMap now contains a mapping of the sub-selection
213 // to the list of (sorted) merged points.
214 // Get its sort order to bundle according to the merged point target.
215 // This is in effect an adjacent list of graph edges to mapping back
216 // to the merged points, but in compact form.
217 // Use the previously obtained newPointCounts for the offsets list.
218
219 labelList lookupMerged(std::move(order));
220 Foam::sortedOrder(subPointMap, lookupMerged);
221
222 // Remap inplace to the original points ids
223 for (label& idx : lookupMerged)
224 {
225 idx = indexer(idx);
226 }
227 // The subPointMap slice is not needed beyond here
228
229
230 // Setup initial identity +1 mapping for pointToUnique
231 // The +1 allows negatives to mark duplicates
232
233 ListOps::identity(pointToUnique, 1);
234
235 // The newPointCounts is an offsets table that we use to walk
236 // across the adjacency list (lookupMerged), picking the original
237 // point with the lowest id as the one to retain (master).
238 {
239 label beg = 0;
240 for (const label len : newPointCounts)
241 {
242 if (!len) continue; // Can be empty
243
244 const label end = (beg + len);
245
246 // Pass 1:
247 // Find the 'master' (lowest point id)
248
249 label masterPointi = lookupMerged[beg];
250
251 for (label iter = beg + 1; iter < end; ++iter)
252 {
253 const label origPointi = lookupMerged[iter];
254
255 if (masterPointi > origPointi)
256 {
257 masterPointi = origPointi;
258 }
259 }
260
261 // Pass 2:
262 // Markup duplicate points, encoding information about master
263 for (label iter = beg; iter < end; ++iter)
264 {
265 const label origPointi = lookupMerged[iter];
266
267 if (masterPointi != origPointi)
268 {
269 // Encode the originating 'master' point
270 pointToUnique[origPointi] = (-masterPointi-1);
271 }
272 }
273
274 beg = end;
275 }
276 }
277
278 // Now have all the information needed
279
280 uniquePoints.resize_nocopy(nUniqPoints);
281 {
282 label uniquei = 0;
283
284 forAll(pointToUnique, pointi)
285 {
286 const label origPointi = pointToUnique[pointi];
287
288 if (origPointi > 0)
289 {
290 // Subtract one to align addressing
291 uniquePoints[uniquei] = (origPointi - 1);
292 pointToUnique[pointi] = uniquei;
293 ++uniquei;
294 }
295 else
296 {
297 // A duplicate point. Also guaranteed that the 'master' point
298 // has a lower index and thus already been seen.
299 const label masterPointi = mag(origPointi) - 1;
300 pointToUnique[pointi] = pointToUnique[masterPointi];
301 }
302 }
303 }
304
305 return nDupPoints;
306}
307
308
309// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
310
311template<class PointList>
312Foam::label Foam::mergePoints
313(
314 const PointList& points,
315 labelList& pointToUnique,
316 labelList& uniquePoints,
317 const scalar mergeTol,
318 const bool verbose
319)
320{
321 const label nTotPoints = points.size();
322
323 if (!nTotPoints)
324 {
325 // Nothing to do
326 pointToUnique.clear();
327 uniquePoints.clear();
328 return 0; // No points removed
329 }
330
332 (
333 points,
334 identityOp(), // identity indexer
335 nTotPoints, // == nSubPoints
336 pointToUnique,
337 uniquePoints,
338 mergeTol,
339 verbose
340 );
341}
342
343
344template<class PointList>
345Foam::label Foam::mergePoints
346(
347 const PointList& points,
348 const labelUList& selection,
349 labelList& pointToUnique,
350 labelList& uniquePoints,
351 const scalar mergeTol,
352 const bool verbose
353)
354{
355 const label nTotPoints = points.size();
356 const label nSubPoints = selection.size();
357
358 if (!nTotPoints || !nSubPoints)
359 {
360 // Nothing to do
361 pointToUnique.clear();
362 uniquePoints.clear();
363 return 0; // No points removed
364 }
365
366 const auto indexer = [&](const label i) -> label { return selection[i]; };
367
369 (
370 points,
371 indexer,
372 nSubPoints,
373 pointToUnique,
374 uniquePoints,
375 mergeTol,
376 verbose
377 );
378}
379
380
381template<class PointList>
382Foam::label Foam::mergePoints
383(
384 const PointList& points,
385 const scalar mergeTol,
386 const bool verbose,
387 labelList& pointToUnique
388)
389{
390 labelList uniquePoints;
391 const label nChanged = Foam::mergePoints
392 (
393 points,
394 pointToUnique,
395 uniquePoints,
396 mergeTol,
397 verbose
398 );
399
400 // Number of unique points
401 return (points.size() - nChanged);
402}
403
404
405template<class PointList>
406Foam::label Foam::inplaceMergePoints
407(
408 PointList& points,
409 const scalar mergeTol,
410 const bool verbose,
411 labelList& pointToUnique
412)
413{
414 labelList uniquePoints;
415 const label nChanged = Foam::mergePoints
416 (
417 points,
418 pointToUnique,
419 uniquePoints,
420 mergeTol,
421 verbose
422 );
423
424 if (nChanged)
425 {
426 // Overwrite
427 points = List<typename PointList::value_type>(points, uniquePoints);
428 }
429 else
430 {
431 // TDB:
432 // pointToUnique.clear();
433 }
434
435 return nChanged;
436}
437
438
439template<class PointList>
440Foam::label Foam::inplaceMergePoints
441(
442 PointList& points,
443 const labelUList& selection,
444 const scalar mergeTol,
445 const bool verbose,
446 labelList& pointToUnique
447)
448{
449 labelList uniquePoints;
450 const label nChanged = Foam::mergePoints
451 (
452 points,
453 selection,
454 pointToUnique,
455 uniquePoints,
456 mergeTol,
457 verbose
458 );
459
460 if (nChanged)
461 {
462 // Overwrite
463 points = List<typename PointList::value_type>(points, uniquePoints);
464 }
465 else
466 {
467 // TDB:
468 // pointToUnique.clear();
469 }
470
471 return nChanged;
472}
473
474
475template<class PointList>
476Foam::label Foam::mergePoints
477(
478 const PointList& points,
479 const scalar mergeTol,
480 const bool verbose,
481 labelList& pointToUnique,
482 List<typename PointList::value_type>& newPoints
483)
484{
485 const label nTotPoints = points.size();
486
487 if (!nTotPoints)
488 {
489 // Nothing to do
490 pointToUnique.clear();
491 newPoints.clear();
492 return 0; // No points removed
493 }
494
495 labelList uniquePoints;
496 const label nChanged = Foam::mergePoints
497 (
498 points,
499 pointToUnique,
500 uniquePoints,
501 mergeTol,
502 verbose
503 );
504
505 if (nChanged)
506 {
507 newPoints = List<typename PointList::value_type>(points, uniquePoints);
508 }
509 else
510 {
511 // TDB:
512 // pointToUnique.clear();
513 newPoints = points;
514 }
515
516 return nChanged;
517}
518
519
520// ************************************************************************* //
Various functions to operate on Lists.
volScalarField & p
const pointField & points
label mergePoints(const PointList &points, const IndexerOp &indexer, const label nSubPoints, labelList &pointToUnique, labelList &uniquePoints, const scalar mergeTol, const bool verbose)
Implementation detail for Foam::mergePoints.
labelList identity(const label len, label start=0)
Return an identity map of the given length with (map[i] == i)
Definition: labelList.C:38
List< label > labelList
A List of labels.
Definition: List.H:66
label mergePoints(const PointList &points, labelList &pointToUnique, labelList &uniquePoints, const scalar mergeTol=SMALL, const bool verbose=false)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:372
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
labelList sortedOrder(const UList< T > &input)
Return the (stable) sort order for the list.
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:33
label inplaceMergePoints(PointList &points, const scalar mergeTol, const bool verbose, labelList &pointToUnique)
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
dimensioned< typename typeOfMag< Type >::type > magSqr(const dimensioned< Type > &dt)
constexpr auto end(C &c) -> decltype(c.end())
Return iterator to the end of the container c.
Definition: stdFoam.H:158
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:333