surfaceFeatures.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-2024 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
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 "surfaceFeatures.H"
30 #include "triSurface.H"
31 #include "indexedOctree.H"
32 #include "treeDataEdge.H"
33 #include "treeDataPoint.H"
34 #include "meshTools.H"
35 #include "Fstream.H"
36 #include "unitConversion.H"
37 #include "edgeHashes.H"
38 
39 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
40 
41 namespace Foam
42 {
43  defineTypeNameAndDebug(surfaceFeatures, 0);
44 
45  const scalar surfaceFeatures::parallelTolerance = sin(degToRad(1.0));
46 
47 
49 // Check if the point is on the line
50 static bool onLine(const Foam::point& p, const linePointRef& line)
51 {
52  const point& a = line.start();
53  const point& b = line.end();
54 
55  if
56  (
57  (p.x() < min(a.x(), b.x()) || p.x() > max(a.x(), b.x()))
58  || (p.y() < min(a.y(), b.y()) || p.y() > max(a.y(), b.y()))
59  || (p.z() < min(a.z(), b.z()) || p.z() > max(a.z(), b.z()))
60  )
61  {
62  return false;
63  }
64 
65  return true;
66 }
68 
69 } // End namespace Foam
70 
71 
72 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
73 
74 Foam::pointIndexHit Foam::surfaceFeatures::edgeNearest
75 (
76  const linePointRef& line,
77  const point& sample
78 )
79 {
80  pointHit eHit = line.nearestDist(sample);
81 
82  // Classification of position on edge.
83  label endPoint;
84 
85  if (eHit.hit())
86  {
87  // Nearest point is on edge itself.
88  // Note: might be at or very close to endpoint. We should use tolerance
89  // here.
90  endPoint = -1;
91  }
92  else
93  {
94  // Nearest point has to be one of the end points. Find out
95  // which one.
96  if
97  (
98  eHit.point().distSqr(line.start())
99  < eHit.point().distSqr(line.end())
100  )
101  {
102  endPoint = 0;
103  }
104  else
105  {
106  endPoint = 1;
107  }
108  }
109 
110  return pointIndexHit(eHit, endPoint);
111 }
112 
113 
114 // Go from selected edges only to a value for every edge
116  const
117 {
118  List<edgeStatus> edgeStat(surf_.nEdges(), NONE);
119 
120  // Region edges first
121  for (label i = 0; i < externalStart_; i++)
122  {
123  edgeStat[featureEdges_[i]] = REGION;
124  }
125 
126  // External edges
127  for (label i = externalStart_; i < internalStart_; i++)
128  {
129  edgeStat[featureEdges_[i]] = EXTERNAL;
130  }
131 
132  // Internal edges
133  for (label i = internalStart_; i < featureEdges_.size(); i++)
134  {
135  edgeStat[featureEdges_[i]] = INTERNAL;
136  }
138  return edgeStat;
139 }
140 
141 
142 // Set from value for every edge
144 (
145  const List<edgeStatus>& edgeStat,
146  const scalar includedAngle
147 )
148 {
149  // Count
150 
151  label nRegion = 0;
152  label nExternal = 0;
153  label nInternal = 0;
154 
155  forAll(edgeStat, edgeI)
156  {
157  if (edgeStat[edgeI] == REGION)
158  {
159  nRegion++;
160  }
161  else if (edgeStat[edgeI] == EXTERNAL)
162  {
163  nExternal++;
164  }
165  else if (edgeStat[edgeI] == INTERNAL)
166  {
167  nInternal++;
168  }
169  }
170 
171  externalStart_ = nRegion;
172  internalStart_ = externalStart_ + nExternal;
173 
174 
175  // Copy
176 
177  featureEdges_.setSize(internalStart_ + nInternal);
178 
179  label regionI = 0;
180  label externalI = externalStart_;
181  label internalI = internalStart_;
182 
183  forAll(edgeStat, edgeI)
184  {
185  if (edgeStat[edgeI] == REGION)
186  {
187  featureEdges_[regionI++] = edgeI;
188  }
189  else if (edgeStat[edgeI] == EXTERNAL)
190  {
191  featureEdges_[externalI++] = edgeI;
192  }
193  else if (edgeStat[edgeI] == INTERNAL)
194  {
195  featureEdges_[internalI++] = edgeI;
196  }
197  }
198 
199  const scalar minCos = Foam::cos(degToRad(180.0 - includedAngle));
200 
201  calcFeatPoints(edgeStat, minCos);
202 }
203 
204 
205 //construct feature points where more than 2 feature edges meet
206 void Foam::surfaceFeatures::calcFeatPoints
207 (
208  const List<edgeStatus>& edgeStat,
209  const scalar minCos
210 )
211 {
212  DynamicList<label> featurePoints(surf_.nPoints()/1000);
213 
214  const labelListList& pointEdges = surf_.pointEdges();
215  const edgeList& edges = surf_.edges();
216  const pointField& localPoints = surf_.localPoints();
217 
218  forAll(pointEdges, pointi)
219  {
220  const labelList& pEdges = pointEdges[pointi];
221 
222  label nFeatEdges = 0;
223 
224  forAll(pEdges, i)
225  {
226  if (edgeStat[pEdges[i]] != NONE)
227  {
228  nFeatEdges++;
229  }
230  }
231 
232  if (nFeatEdges > 2)
233  {
234  featurePoints.append(pointi);
235  }
236  else if (nFeatEdges == 2)
237  {
238  // Check the angle between the two edges
239  DynamicList<vector> edgeVecs(2);
240 
241  forAll(pEdges, i)
242  {
243  const label edgeI = pEdges[i];
244 
245  if (edgeStat[edgeI] != NONE)
246  {
247  vector vec = edges[edgeI].vec(localPoints);
248  scalar magVec = mag(vec);
249  if (magVec > SMALL)
250  {
251  edgeVecs.append(vec/magVec);
252  }
253  }
254  }
255 
256  if (edgeVecs.size() == 2 && mag(edgeVecs[0] & edgeVecs[1]) < minCos)
257  {
258  featurePoints.append(pointi);
259  }
260  }
261  }
262 
263  featurePoints_.transfer(featurePoints);
264 }
265 
266 
267 void Foam::surfaceFeatures::classifyFeatureAngles
268 (
269  const labelListList& edgeFaces,
270  List<edgeStatus>& edgeStat,
271  const scalar minCos,
272  const bool geometricTestOnly
273 ) const
274 {
275  const vectorField& faceNormals = surf_.faceNormals();
276  const pointField& points = surf_.points();
277 
278  // Special case: minCos=1
279  bool selectAll = (mag(minCos-1.0) < SMALL);
280 
281  forAll(edgeFaces, edgeI)
282  {
283  const labelList& eFaces = edgeFaces[edgeI];
284 
285  if (eFaces.size() != 2)
286  {
287  // Non-manifold. What to do here? Is region edge? external edge?
288  edgeStat[edgeI] = REGION;
289  }
290  else
291  {
292  label face0 = eFaces[0];
293  label face1 = eFaces[1];
294 
295  if
296  (
297  !geometricTestOnly
298  && surf_[face0].region() != surf_[face1].region()
299  )
300  {
301  edgeStat[edgeI] = REGION;
302  }
303  else if
304  (
305  selectAll
306  || ((faceNormals[face0] & faceNormals[face1]) < minCos)
307  )
308  {
309  // Check if convex or concave by looking at angle
310  // between face centres and normal
311  vector f0Tof1 =
312  surf_[face1].centre(points)
313  - surf_[face0].centre(points);
314 
315  if ((f0Tof1 & faceNormals[face0]) >= 0.0)
316  {
317  edgeStat[edgeI] = INTERNAL;
318  }
319  else
320  {
321  edgeStat[edgeI] = EXTERNAL;
322  }
323  }
324  }
325  }
326 }
327 
328 
329 // Returns next feature edge connected to pointi with correct value.
330 Foam::label Foam::surfaceFeatures::nextFeatEdge
331 (
332  const List<edgeStatus>& edgeStat,
333  const labelList& featVisited,
334  const label unsetVal,
335  const label prevEdgeI,
336  const label vertI
337 ) const
338 {
339  const labelList& pEdges = surf_.pointEdges()[vertI];
340 
341  label nextEdgeI = -1;
342 
343  forAll(pEdges, i)
344  {
345  label edgeI = pEdges[i];
346 
347  if
348  (
349  edgeI != prevEdgeI
350  && edgeStat[edgeI] != NONE
351  && featVisited[edgeI] == unsetVal
352  )
353  {
354  if (nextEdgeI == -1)
355  {
356  nextEdgeI = edgeI;
357  }
358  else
359  {
360  // More than one feature edge to choose from. End of segment.
361  return -1;
362  }
363  }
364  }
365 
366  return nextEdgeI;
367 }
368 
369 
370 // Finds connected feature edges by walking from prevEdgeI in direction of
371 // prevPointi. Marks feature edges visited in featVisited by assigning them
372 // the current feature line number. Returns cumulative length of edges walked.
373 // Works in one of two modes:
374 // - mark : step to edges with featVisited = -1.
375 // Mark edges visited with currentFeatI.
376 // - clear : step to edges with featVisited = currentFeatI
377 // Mark edges visited with -2 and erase from feature edges.
378 Foam::surfaceFeatures::labelScalar Foam::surfaceFeatures::walkSegment
379 (
380  const bool mark,
381  const List<edgeStatus>& edgeStat,
382  const label startEdgeI,
383  const label startPointi,
384  const label currentFeatI,
385  labelList& featVisited
386 )
387 {
388  label edgeI = startEdgeI;
389 
390  label vertI = startPointi;
391 
392  scalar visitedLength = 0.0;
393 
394  label nVisited = 0;
395 
396  if (featurePoints_.found(startPointi))
397  {
398  // Do not walk across feature points
399 
400  return labelScalar(nVisited, visitedLength);
401  }
402 
403  //
404  // Now we have:
405  // edgeI : first edge on this segment
406  // vertI : one of the endpoints of this segment
407  //
408  // Start walking, marking off edges (in featVisited)
409  // as we go along.
410  //
411 
412  label unsetVal;
413 
414  if (mark)
415  {
416  unsetVal = -1;
417  }
418  else
419  {
420  unsetVal = currentFeatI;
421  }
422 
423  do
424  {
425  // Step to next feature edge with value unsetVal
426  edgeI = nextFeatEdge(edgeStat, featVisited, unsetVal, edgeI, vertI);
427 
428  if (edgeI == -1 || edgeI == startEdgeI)
429  {
430  break;
431  }
432 
433  // Mark with current value. If in clean mode also remove feature edge.
434 
435  if (mark)
436  {
437  featVisited[edgeI] = currentFeatI;
438  }
439  else
440  {
441  featVisited[edgeI] = -2;
442  }
443 
444 
445  // Step to next vertex on edge
446  const edge& e = surf_.edges()[edgeI];
447 
448  vertI = e.otherVertex(vertI);
449 
450 
451  // Update cumulative length
452  visitedLength += e.mag(surf_.localPoints());
453 
454  nVisited++;
455 
456  if (nVisited > surf_.nEdges())
457  {
458  Warning<< "walkSegment : reached iteration limit in walking "
459  << "feature edges on surface from edge:" << startEdgeI
460  << " vertex:" << startPointi << nl
461  << "Returning with large length" << endl;
462 
463  return labelScalar(nVisited, GREAT);
464  }
465  }
466  while (true);
467 
468  return labelScalar(nVisited, visitedLength);
469 }
470 
471 
472 //- Divide into multiple normal bins
473 // - return REGION if != 2 normals
474 // - return REGION if 2 normals that make feature angle
475 // - otherwise return NONE and set normals,bins
476 //
477 // This has been relocated from surfaceFeatureExtract and could be cleaned
478 // up some more.
479 //
481 Foam::surfaceFeatures::surfaceFeatures::checkFlatRegionEdge
482 (
483  const scalar tol,
484  const scalar includedAngle,
485  const label edgeI,
486  const point& leftPoint
487 ) const
488 {
489  const triSurface& surf = surf_;
490 
491  const edge& e = surf.edges()[edgeI];
492  const labelList& eFaces = surf.edgeFaces()[edgeI];
493 
494  // Bin according to normal
495 
496  DynamicList<vector> normals(2);
497  DynamicList<labelList> bins(2);
498 
499  forAll(eFaces, eFacei)
500  {
501  const vector& n = surf.faceNormals()[eFaces[eFacei]];
502 
503  // Find the normal in normals
504  label index = -1;
505  forAll(normals, normalI)
506  {
507  if (mag(n & normals[normalI]) > (1-tol))
508  {
509  index = normalI;
510  break;
511  }
512  }
513 
514  if (index != -1)
515  {
516  bins[index].append(eFacei);
517  }
518  else if (normals.size() >= 2)
519  {
520  // Would be third normal. Mark as feature.
521  //Pout<< "** at edge:" << surf.localPoints()[e[0]]
522  // << surf.localPoints()[e[1]]
523  // << " have normals:" << normals
524  // << " and " << n << endl;
526  }
527  else
528  {
529  normals.append(n);
530  bins.append(labelList(1, eFacei));
531  }
532  }
533 
534 
535  // Check resulting number of bins
536  if (bins.size() == 1)
537  {
538  // Note: should check here whether they are two sets of faces
539  // that are planar or indeed 4 faces al coming together at an edge.
540  //Pout<< "** at edge:"
541  // << surf.localPoints()[e[0]]
542  // << surf.localPoints()[e[1]]
543  // << " have single normal:" << normals[0]
544  // << endl;
545  return surfaceFeatures::NONE;
546  }
547  else
548  {
549  // Two bins. Check if normals make an angle
550 
551  //Pout<< "** at edge:"
552  // << surf.localPoints()[e[0]]
553  // << surf.localPoints()[e[1]] << nl
554  // << " normals:" << normals << nl
555  // << " bins :" << bins << nl
556  // << endl;
557 
558  if (includedAngle >= 0)
559  {
560  scalar minCos = Foam::cos(degToRad(180.0 - includedAngle));
561 
562  forAll(eFaces, i)
563  {
564  const vector& ni = surf.faceNormals()[eFaces[i]];
565  for (label j=i+1; j<eFaces.size(); j++)
566  {
567  const vector& nj = surf.faceNormals()[eFaces[j]];
568  if (mag(ni & nj) < minCos)
569  {
570  //Pout<< "have sharp feature between normal:" << ni
571  // << " and " << nj << endl;
572 
573  // Is feature. Keep as region or convert to
574  // feature angle? For now keep as region.
576  }
577  }
578  }
579  }
580 
581  // So now we have two normals bins but need to make sure both
582  // bins have the same regions in it.
583 
584  // 1. store + or - region number depending
585  // on orientation of triangle in bins[0]
586  const labelList& bin0 = bins[0];
587  labelList regionAndNormal(bin0.size());
588  forAll(bin0, i)
589  {
590  const labelledTri& t = surf.localFaces()[eFaces[bin0[i]]];
591  const auto dir = t.edgeDirection(e);
592 
593  if (dir > 0)
594  {
595  regionAndNormal[i] = t.region()+1;
596  }
597  else if (dir == 0)
598  {
600  << exit(FatalError);
601  }
602  else
603  {
604  regionAndNormal[i] = -(t.region()+1);
605  }
606  }
607 
608  // 2. check against bin1
609  const labelList& bin1 = bins[1];
610  labelList regionAndNormal1(bin1.size());
611  forAll(bin1, i)
612  {
613  const labelledTri& t = surf.localFaces()[eFaces[bin1[i]]];
614  const auto dir = t.edgeDirection(e);
615 
616  label myRegionAndNormal;
617  if (dir > 0)
618  {
619  myRegionAndNormal = t.region()+1;
620  }
621  else
622  {
623  myRegionAndNormal = -(t.region()+1);
624  }
625 
626  regionAndNormal1[i] = myRegionAndNormal;
627 
628  label index = regionAndNormal.find(-myRegionAndNormal);
629  if (index == -1)
630  {
631  // Not found.
632  //Pout<< "cannot find region " << myRegionAndNormal
633  // << " in regions " << regionAndNormal << endl;
634 
636  }
637  }
638 
639  // Passed all checks, two normal bins with the same contents.
640  //Pout<< "regionAndNormal:" << regionAndNormal << endl;
641  //Pout<< "myRegionAndNormal:" << regionAndNormal1 << endl;
642  }
643 
644  return surfaceFeatures::NONE;
645 }
646 
647 /*
648 
649 // TBD. Big problem for duplicate triangles with opposing normals: we
650 // don't know which one of the duplicates gets found on either side so
651 // the normal might be + or -. Hence on e.g. motorBike.obj we see feature
652 // lines where there shouldn't be
653 Foam::surfaceFeatures::edgeStatus
654 Foam::surfaceFeatures::surfaceFeatures::checkBinRegion
655 (
656  const label edgei,
657  const labelList& bin0,
658  const labelList& bin1
659 ) const
660 {
661  const triSurface& surf = surf_;
662  const labelList& eFaces = surf.edgeFaces()[edgei];
663  const edge& e = surf.edges()[edgei];
664 
665  // 1. store + or - region number depending
666  // on orientation of triangle in bins[0]
667  labelList regionAndNormal(bin0.size());
668  forAll(bin0, i)
669  {
670  const labelledTri& t = surf.localFaces()[eFaces[bin0[i]]];
671  const auto dir = t.edgeDirection(e);
672 
673  if (dir > 0)
674  {
675  regionAndNormal[i] = t.region()+1;
676  }
677  else if (dir == 0)
678  {
679  FatalErrorInFunction
680  << exit(FatalError);
681  }
682  else
683  {
684  regionAndNormal[i] = -(t.region()+1);
685  }
686  }
687 
688  // 2. check against bin1
689  labelList regionAndNormal1(bin1.size());
690  forAll(bin1, i)
691  {
692  const labelledTri& t = surf.localFaces()[eFaces[bin1[i]]];
693  const auto dir = t.edgeDirection(e);
694 
695  label myRegionAndNormal;
696  if (dir > 0)
697  {
698  myRegionAndNormal = t.region()+1;
699  }
700  else if (dir == 0)
701  {
702  myRegionAndNormal = 0;
703  FatalErrorInFunction
704  << exit(FatalError);
705  }
706  else
707  {
708  myRegionAndNormal = -(t.region()+1);
709  }
710 
711  regionAndNormal1[i] = myRegionAndNormal;
712 
713  label index = regionAndNormal.find(-myRegionAndNormal);
714  if (index == -1)
715  {
716  // Not found.
717  //Pout<< "cannot find region " << myRegionAndNormal
718  // << " in regions " << regionAndNormal << endl;
719 
720  return surfaceFeatures::REGION;
721  }
722  }
723  return surfaceFeatures::NONE;
724 }
725 
726 
727 Foam::surfaceFeatures::edgeStatus
728 Foam::surfaceFeatures::surfaceFeatures::checkFlatRegionEdge
729 (
730  const scalar tol,
731  const scalar includedAngle,
732  const label edgei,
733  const point& leftPoint
734 ) const
735 {
736  const triSurface& surf = surf_;
737  const edge& e = surf.edges()[edgei];
738  const auto& mp = surf.meshPoints();
739  const point eMid(edge(mp[e[0]], mp[e[1]]).centre(surf.points()));
740  const labelList& eFaces = surf.edgeFaces()[edgei];
741 
742  vector leftVec(leftPoint-eMid);
743  leftVec.normalise();
744 
745  // Bin according to normal and location w.r.t. first face
746  plane pl(eMid, leftVec);
747 
748 
749  DynamicList<labelList> leftBins(2);
750  DynamicList<labelList> rightBins(2);
751  DynamicList<vector> leftNormals(2);
752  DynamicList<vector> rightNormals(2);
753 
754  // Append first face since this is what leftPoint was created from in the
755  // first place
756  leftNormals.append(surf.faceNormals()[eFaces[0]]);
757  leftBins.append(labelList(1, 0));
758 
759  for (label eFacei = 1; eFacei < eFaces.size(); ++eFacei)
760  {
761  const label facei = eFaces[eFacei];
762 
763  const bool isLeft(pl.signedDistance(surf.faceCentres()[facei]) > 0);
764 
765  DynamicList<labelList>& bins = (isLeft ? leftBins : rightBins);
766  DynamicList<vector>& normals = (isLeft ? leftNormals : rightNormals);
767 
768  const vector& n = surf.faceNormals()[facei];
769 
770  // Find the normal in normals
771  label index = -1;
772  forAll(normals, normalI)
773  {
774  if (mag(n & normals[normalI]) > (1-tol))
775  {
776  index = normalI;
777  break;
778  }
779  }
780 
781  if (index != -1)
782  {
783  bins[index].append(eFacei);
784 // Pout<< "edge:" << edgei << " verts:" << e
785 // << " found existing normal bin:" << index
786 // << " after:" << flatOutput(bins[index])
787 // << endl;
788  }
789  else if ((leftNormals.size()+rightNormals.size()) >= 2)
790  {
791  // Would be third normal. Mark as feature.
792  //Pout<< "** at edge:" << surf.localPoints()[e[0]]
793  // << surf.localPoints()[e[1]]
794  // << " have normals:" << normals
795  // << " and " << n << endl;
796  return surfaceFeatures::REGION;
797  }
798  else
799  {
800  normals.append(n);
801  bins.append(labelList(1, eFacei));
802  }
803  }
804 
805  // Check resulting number of bins
806  if ((leftBins.size()+rightBins.size()) == 1)
807  {
808  // Note: should check here whether they are two sets of faces
809  // that are planar or indeed 4 faces al coming together at an edge.
810  //Pout<< "** at edge:"
811  // << surf.localPoints()[e[0]]
812  // << surf.localPoints()[e[1]]
813  // << " have single normal:" << normals[0]
814  // << endl;
815  return surfaceFeatures::NONE;
816  }
817  else
818  {
819  // Two bins. Check if normals make an angle
820 
821  //Pout<< "** at edge:"
822  // << surf.localPoints()[e[0]]
823  // << surf.localPoints()[e[1]] << nl
824  // << " normals:" << normals << nl
825  // << " bins :" << bins << nl
826  // << endl;
827 
828  if (includedAngle >= 0)
829  {
830  scalar minCos = Foam::cos(degToRad(180.0 - includedAngle));
831 
832  forAll(eFaces, i)
833  {
834  const vector& ni = surf.faceNormals()[eFaces[i]];
835  for (label j=i+1; j<eFaces.size(); j++)
836  {
837  const vector& nj = surf.faceNormals()[eFaces[j]];
838  if (mag(ni & nj) < minCos)
839  {
840  //Pout<< "have sharp feature between normal:" << ni
841  // << " and " << nj << endl;
842 
843  // Is feature. Keep as region or convert to
844  // feature angle? For now keep as region.
845  return surfaceFeatures::REGION;
846  }
847  }
848  }
849  }
850 
851  // So now we have two normals bins but need to make sure both
852  // bins have the same regions in it.
853 
854  if (leftBins.size() == 2)
855  {
856  return checkBinRegion
857  (
858  edgei,
859  leftBins[0],
860  leftBins[1]
861  );
862  }
863  else if (leftBins.size() == 1 && rightBins.size() == 1)
864  {
865  return checkBinRegion
866  (
867  edgei,
868  leftBins[0],
869  rightBins[0]
870  );
871  }
872  else if (rightBins.size() == 2)
873  {
874  return checkBinRegion
875  (
876  edgei,
877  rightBins[0],
878  rightBins[1]
879  );
880  }
881  else
882  {
883  FatalErrorInFunction << "leftBins:" << leftBins
884  << " rightBins:" << rightBins << exit(FatalError);
885  }
886  }
887 
888  return surfaceFeatures::NONE;
889 }
890 */
891 
892 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
893 
895 :
896  surf_(surf),
897  featurePoints_(0),
898  featureEdges_(0),
899  externalStart_(0),
900  internalStart_(0)
901 {}
903 
904 // Construct from components.
906 (
907  const triSurface& surf,
908  const labelList& featurePoints,
909  const labelList& featureEdges,
910  const label externalStart,
911  const label internalStart
912 )
913 :
914  surf_(surf),
915  featurePoints_(featurePoints),
916  featureEdges_(featureEdges),
917  externalStart_(externalStart),
918  internalStart_(externalStart)
919 {}
921 
922 // Construct from surface, angle and min length
924 (
925  const triSurface& surf,
926  const scalar includedAngle,
927  const scalar minLen,
928  const label minElems,
929  const bool geometricTestOnly
930 )
931 :
932  surf_(surf),
933  featurePoints_(0),
934  featureEdges_(0),
935  externalStart_(0),
936  internalStart_(0)
937 {
938  findFeatures(includedAngle, geometricTestOnly);
939 
940  if (minLen > 0 || minElems > 0)
941  {
942  trimFeatures(minLen, minElems, includedAngle);
943  }
944 }
945 
946 
948 (
949  const triSurface& surf,
950  const dictionary& featInfoDict
951 )
952 :
953  surf_(surf),
954  featurePoints_(featInfoDict.lookup("featurePoints")),
955  featureEdges_(featInfoDict.lookup("featureEdges")),
956  externalStart_(featInfoDict.get<label>("externalStart")),
957  internalStart_(featInfoDict.get<label>("internalStart"))
958 {}
959 
960 
962 (
963  const triSurface& surf,
964  const fileName& fName
965 )
966 :
967  surf_(surf),
968  featurePoints_(0),
969  featureEdges_(0),
970  externalStart_(0),
971  internalStart_(0)
972 {
973  IFstream str(fName);
974 
975  dictionary featInfoDict(str);
976 
977  featInfoDict.readEntry("featureEdges", featureEdges_);
978  featInfoDict.readEntry("featurePoints", featurePoints_);
979  featInfoDict.readEntry("externalStart", externalStart_);
980  featInfoDict.readEntry("internalStart", internalStart_);
981 }
982 
983 
985 (
986  const triSurface& surf,
987  const pointField& points,
988  const edgeList& edges,
989  const scalar mergeTol,
990  const bool geometricTestOnly
991 )
992 :
993  surf_(surf),
994  featurePoints_(0),
995  featureEdges_(0),
996  externalStart_(0),
997  internalStart_(0)
998 {
999  // Match edge mesh edges with the triSurface edges
1000 
1001  const labelListList& surfEdgeFaces = surf_.edgeFaces();
1002  const edgeList& surfEdges = surf_.edges();
1003 
1004  scalar mergeTolSqr = sqr(mergeTol);
1005 
1006  EdgeMap<label> dynFeatEdges(2*edges.size());
1007  DynamicList<labelList> dynFeatureEdgeFaces(edges.size());
1008 
1009  labelList edgeLabel;
1010 
1012  (
1013  edges,
1014  points,
1015  mergeTolSqr,
1016  edgeLabel // label of surface edge or -1
1017  );
1018 
1019  label count = 0;
1020  forAll(edgeLabel, sEdgeI)
1021  {
1022  const label sEdge = edgeLabel[sEdgeI];
1023 
1024  if (sEdge == -1)
1025  {
1026  continue;
1027  }
1028 
1029  dynFeatEdges.insert(surfEdges[sEdge], count++);
1030  dynFeatureEdgeFaces.append(surfEdgeFaces[sEdge]);
1031  }
1032 
1033  // Find whether an edge is external or internal
1034  List<edgeStatus> edgeStat(dynFeatEdges.size(), NONE);
1035 
1036  classifyFeatureAngles
1037  (
1038  dynFeatureEdgeFaces,
1039  edgeStat,
1040  GREAT,
1041  geometricTestOnly
1042  );
1043 
1044  // Transfer the edge status to a list encompassing all edges in the surface
1045  // so that calcFeatPoints can be used.
1046  List<edgeStatus> allEdgeStat(surf_.nEdges(), NONE);
1047 
1048  forAll(allEdgeStat, eI)
1049  {
1050  const auto iter = dynFeatEdges.cfind(surfEdges[eI]);
1051 
1052  if (iter.good())
1053  {
1054  allEdgeStat[eI] = edgeStat[iter.val()];
1055  }
1056  }
1057 
1058  edgeStat.clear();
1059  dynFeatEdges.clear();
1060 
1061  setFromStatus(allEdgeStat, GREAT);
1062 }
1063 
1064 
1066 :
1067  surf_(sf.surface()),
1068  featurePoints_(sf.featurePoints()),
1069  featureEdges_(sf.featureEdges()),
1070  externalStart_(sf.externalStart()),
1071  internalStart_(sf.internalStart())
1072 {}
1073 
1075 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
1076 
1078 (
1079  const bool regionEdges,
1080  const bool externalEdges,
1081  const bool internalEdges
1082 ) const
1083 {
1084  DynamicList<label> selectedEdges;
1085 
1086  if (regionEdges)
1087  {
1088  selectedEdges.setCapacity(selectedEdges.size() + nRegionEdges());
1089 
1090  for (label i = 0; i < externalStart_; i++)
1091  {
1092  selectedEdges.append(featureEdges_[i]);
1093  }
1094  }
1095 
1096  if (externalEdges)
1097  {
1098  selectedEdges.setCapacity(selectedEdges.size() + nExternalEdges());
1099 
1100  for (label i = externalStart_; i < internalStart_; i++)
1101  {
1102  selectedEdges.append(featureEdges_[i]);
1103  }
1104  }
1105 
1106  if (internalEdges)
1107  {
1108  selectedEdges.setCapacity(selectedEdges.size() + nInternalEdges());
1109 
1110  for (label i = internalStart_; i < featureEdges_.size(); i++)
1111  {
1112  selectedEdges.append(featureEdges_[i]);
1113  }
1114  }
1115 
1116  return selectedEdges.shrink();
1118 
1119 
1121 (
1122  const scalar includedAngle,
1123  const bool geometricTestOnly
1124 )
1125 {
1126  scalar minCos = Foam::cos(degToRad(180.0 - includedAngle));
1127 
1128  // Per edge whether is feature edge.
1129  List<edgeStatus> edgeStat(surf_.nEdges(), NONE);
1130 
1131  classifyFeatureAngles
1132  (
1133  surf_.edgeFaces(),
1134  edgeStat,
1135  minCos,
1136  geometricTestOnly
1137  );
1138 
1139  setFromStatus(edgeStat, includedAngle);
1140 }
1141 
1143 // Remove small strings of edges. First assigns string number to
1144 // every edge and then works out the length of them.
1146 (
1147  const scalar minLen,
1148  const label minElems,
1149  const scalar includedAngle
1150 )
1151 {
1152  // Convert feature edge list to status per edge.
1153  List<edgeStatus> edgeStat(toStatus());
1154 
1155 
1156  // Mark feature edges according to connected lines.
1157  // -1: unassigned
1158  // -2: part of too small a feature line
1159  // >0: feature line number
1160  labelList featLines(surf_.nEdges(), -1);
1161 
1162  // Current featureline number.
1163  label featI = 0;
1164 
1165  // Current starting edge
1166  label startEdgeI = 0;
1167 
1168  do
1169  {
1170  // Find unset featureline
1171  for (; startEdgeI < edgeStat.size(); startEdgeI++)
1172  {
1173  if
1174  (
1175  edgeStat[startEdgeI] != NONE
1176  && featLines[startEdgeI] == -1
1177  )
1178  {
1179  // Found unassigned feature edge.
1180  break;
1181  }
1182  }
1183 
1184  if (startEdgeI == edgeStat.size())
1185  {
1186  // No unset feature edge found. All feature edges have line number
1187  // assigned.
1188  break;
1189  }
1190 
1191  featLines[startEdgeI] = featI;
1192 
1193  const edge& startEdge = surf_.edges()[startEdgeI];
1194 
1195  // Walk 'left' and 'right' from startEdge.
1196  labelScalar leftPath =
1197  walkSegment
1198  (
1199  true, // 'mark' mode
1200  edgeStat,
1201  startEdgeI, // edge, not yet assigned to a featureLine
1202  startEdge[0], // start of edge
1203  featI, // Mark value
1204  featLines // Mark for all edges
1205  );
1206 
1207  labelScalar rightPath =
1208  walkSegment
1209  (
1210  true,
1211  edgeStat,
1212  startEdgeI,
1213  startEdge[1], // end of edge
1214  featI,
1215  featLines
1216  );
1217 
1218  if
1219  (
1220  (
1221  leftPath.len_
1222  + rightPath.len_
1223  + startEdge.mag(surf_.localPoints())
1224  < minLen
1225  )
1226  || (leftPath.n_ + rightPath.n_ + 1 < minElems)
1227  )
1228  {
1229  // Rewalk same route (recognizable by featLines == featI)
1230  // to reset featLines.
1231 
1232  featLines[startEdgeI] = -2;
1233 
1234  walkSegment
1235  (
1236  false, // 'clean' mode
1237  edgeStat,
1238  startEdgeI, // edge, not yet assigned to a featureLine
1239  startEdge[0], // start of edge
1240  featI, // Unset value
1241  featLines // Mark for all edges
1242  );
1243 
1244  walkSegment
1245  (
1246  false,
1247  edgeStat,
1248  startEdgeI,
1249  startEdge[1], // end of edge
1250  featI,
1251  featLines
1252  );
1253  }
1254  else
1255  {
1256  featI++;
1257  }
1258  }
1259  while (true);
1260 
1261  // Unmark all feature lines that have featLines=-2
1262  forAll(featureEdges_, i)
1263  {
1264  label edgeI = featureEdges_[i];
1265 
1266  if (featLines[edgeI] == -2)
1267  {
1268  edgeStat[edgeI] = NONE;
1269  }
1270  }
1271 
1272  // Convert back to edge labels
1273  setFromStatus(edgeStat, includedAngle);
1274 
1275  return featLines;
1277 
1278 
1280 (
1281  List<edgeStatus>& edgeStat,
1282  const treeBoundBox& bb
1283 ) const
1284 {
1285  deleteBox(edgeStat, bb, true);
1287 
1288 
1290 (
1291  List<edgeStatus>& edgeStat,
1292  const treeBoundBox& bb
1293 ) const
1294 {
1295  deleteBox(edgeStat, bb, false);
1297 
1298 
1300 (
1301  List<edgeStatus>& edgeStat,
1302  const treeBoundBox& bb,
1303  const bool removeInside
1304 ) const
1305 {
1306  const edgeList& surfEdges = surf_.edges();
1307  const pointField& surfLocalPoints = surf_.localPoints();
1308 
1309  forAll(edgeStat, edgei)
1310  {
1311  const point eMid = surfEdges[edgei].centre(surfLocalPoints);
1312 
1313  if (removeInside ? bb.contains(eMid) : !bb.contains(eMid))
1314  {
1315  edgeStat[edgei] = surfaceFeatures::NONE;
1316  }
1317  }
1319 
1320 
1322 (
1323  List<edgeStatus>& edgeStat,
1324  const plane& cutPlane
1325 ) const
1326 {
1327  const edgeList& surfEdges = surf_.edges();
1328  const pointField& pts = surf_.points();
1329  const labelList& meshPoints = surf_.meshPoints();
1330 
1331  forAll(edgeStat, edgei)
1332  {
1333  const edge& e = surfEdges[edgei];
1334 
1335  const point& p0 = pts[meshPoints[e.start()]];
1336  const point& p1 = pts[meshPoints[e.end()]];
1337  const linePointRef line(p0, p1);
1338 
1339  // If edge does not intersect the plane, delete.
1340  scalar intersect = cutPlane.lineIntersect(line);
1341 
1342  point featPoint = intersect * (p1 - p0) + p0;
1343 
1344  if (!onLine(featPoint, line))
1345  {
1346  edgeStat[edgei] = surfaceFeatures::NONE;
1347  }
1348  }
1350 
1351 
1353 (
1354  List<edgeStatus>& edgeStat
1355 ) const
1356 {
1357  forAll(edgeStat, edgei)
1358  {
1359  if (surf_.edgeFaces()[edgei].size() == 1)
1360  {
1361  edgeStat[edgei] = surfaceFeatures::NONE;
1362  }
1363  }
1365 
1366 
1368 (
1369  List<edgeStatus>& edgeStat
1370 ) const
1371 {
1372  forAll(edgeStat, edgei)
1373  {
1374  if (surf_.edgeFaces()[edgei].size() > 2)
1375  {
1376  edgeStat[edgei] = surfaceFeatures::NONE;
1377  }
1378  }
1379 }
1380 
1381 
1382 //- Divide into multiple normal bins
1383 // - return REGION if != 2 normals
1384 // - return REGION if 2 normals that make feature angle
1385 // - otherwise return NONE and set normals,bins
1386 void Foam::surfaceFeatures::checkFlatRegionEdge
1387 (
1388  List<edgeStatus>& edgeStat,
1389  const scalar tol,
1390  const scalar includedAngle
1391 ) const
1392 {
1393  forAll(edgeStat, edgei)
1394  {
1395  if (edgeStat[edgei] == surfaceFeatures::REGION)
1396  {
1397  const labelList& eFaces = surf_.edgeFaces()[edgei];
1398 
1399  if (eFaces.size() > 2 && (eFaces.size() % 2) == 0)
1400  {
1401  const point& leftPoint = surf_.faceCentres()[eFaces[0]];
1402 
1403  edgeStat[edgei] = checkFlatRegionEdge
1404  (
1405  tol,
1406  includedAngle,
1407  edgei,
1408  leftPoint
1409  );
1410  }
1411  }
1412  }
1413 }
1414 
1417 {
1418  dictionary featInfoDict;
1419  featInfoDict.add("externalStart", externalStart_);
1420  featInfoDict.add("internalStart", internalStart_);
1421  featInfoDict.add("featureEdges", featureEdges_);
1422  featInfoDict.add("featurePoints", featurePoints_);
1423 
1424  featInfoDict.write(os);
1425 }
1426 
1428 void Foam::surfaceFeatures::write(const fileName& fName) const
1429 {
1430  OFstream os(fName);
1431  writeDict(os);
1432 }
1433 
1435 void Foam::surfaceFeatures::writeObj(const fileName& prefix) const
1436 {
1437  OFstream regionStr(prefix + "_regionEdges.obj");
1438  Pout<< "Writing region edges to " << regionStr.name() << endl;
1439 
1440  label verti = 0;
1441  for (label i = 0; i < externalStart_; i++)
1442  {
1443  const edge& e = surf_.edges()[featureEdges_[i]];
1444 
1445  meshTools::writeOBJ(regionStr, surf_.localPoints()[e[0]]); verti++;
1446  meshTools::writeOBJ(regionStr, surf_.localPoints()[e[1]]); verti++;
1447  regionStr << "l " << verti-1 << ' ' << verti << endl;
1448  }
1449 
1450 
1451  OFstream externalStr(prefix + "_externalEdges.obj");
1452  Pout<< "Writing external edges to " << externalStr.name() << endl;
1453 
1454  verti = 0;
1455  for (label i = externalStart_; i < internalStart_; i++)
1456  {
1457  const edge& e = surf_.edges()[featureEdges_[i]];
1458 
1459  meshTools::writeOBJ(externalStr, surf_.localPoints()[e[0]]); verti++;
1460  meshTools::writeOBJ(externalStr, surf_.localPoints()[e[1]]); verti++;
1461  externalStr << "l " << verti-1 << ' ' << verti << endl;
1462  }
1463 
1464  OFstream internalStr(prefix + "_internalEdges.obj");
1465  Pout<< "Writing internal edges to " << internalStr.name() << endl;
1466 
1467  verti = 0;
1468  for (label i = internalStart_; i < featureEdges_.size(); i++)
1469  {
1470  const edge& e = surf_.edges()[featureEdges_[i]];
1471 
1472  meshTools::writeOBJ(internalStr, surf_.localPoints()[e[0]]); verti++;
1473  meshTools::writeOBJ(internalStr, surf_.localPoints()[e[1]]); verti++;
1474  internalStr << "l " << verti-1 << ' ' << verti << endl;
1475  }
1476 
1477  OFstream pointStr(prefix + "_points.obj");
1478  Pout<< "Writing feature points to " << pointStr.name() << endl;
1479 
1480  for (const label pointi : featurePoints_)
1481  {
1482  meshTools::writeOBJ(pointStr, surf_.localPoints()[pointi]);
1483  }
1484 }
1485 
1488 {
1489  os << "Feature set:" << nl
1490  << " points : " << this->featurePoints().size() << nl
1491  << " edges : " << this->featureEdges().size() << nl
1492  << " of which" << nl
1493  << " region edges : " << this->nRegionEdges() << nl
1494  << " external edges : " << this->nExternalEdges() << nl
1495  << " internal edges : " << this->nInternalEdges() << endl;
1496 }
1497 
1498 
1499 // Get nearest vertex on patch for every point of surf in pointSet.
1501 (
1502  const labelList& pointLabels,
1503  const pointField& samples,
1504  const scalarField& maxDistSqr
1505 ) const
1506 {
1507  // Build tree out of all samples.
1508 
1509  // Define bound box here (gcc-4.8.5)
1510  const treeBoundBox overallBb(samples);
1511 
1513  (
1515  overallBb,
1516  8, // maxLevel
1517  10, // leafsize
1518  3.0 // duplicity
1519  );
1520  const auto& treeData = ppTree.shapes();
1521 
1522  // From patch point to surface point
1523  Map<label> nearest(2*pointLabels.size());
1524 
1525  const pointField& surfPoints = surf_.localPoints();
1526 
1527  forAll(pointLabels, i)
1528  {
1529  const label surfPointi = pointLabels[i];
1530 
1531  const point& surfPt = surfPoints[surfPointi];
1532 
1533  pointIndexHit info = ppTree.findNearest
1534  (
1535  surfPt,
1536  maxDistSqr[i]
1537  );
1538 
1539  if (!info.hit())
1540  {
1542  << "Problem for point "
1543  << surfPointi << " in tree " << ppTree.bb()
1544  << abort(FatalError);
1545  }
1546 
1547  label sampleI = info.index();
1548 
1549  if (treeData.centre(sampleI).distSqr(surfPt) < maxDistSqr[sampleI])
1550  {
1551  nearest.insert(sampleI, surfPointi);
1552  }
1553  }
1554 
1555 
1556  if (debug)
1557  {
1558  //
1559  // Dump to obj file
1560  //
1561 
1562  Pout<< "Dumping nearest surface feature points to nearestSamples.obj"
1563  << endl;
1564 
1565  OFstream objStream("nearestSamples.obj");
1566 
1567  label vertI = 0;
1568  forAllConstIters(nearest, iter)
1569  {
1570  meshTools::writeOBJ(objStream, samples[iter.key()]); vertI++;
1571  meshTools::writeOBJ(objStream, surfPoints[iter.val()]); vertI++;
1572  objStream<< "l " << vertI-1 << ' ' << vertI << endl;
1573  }
1574  }
1575 
1576  return nearest;
1577 }
1578 
1579 
1580 // Get nearest sample point for regularly sampled points along
1581 // selected edges. Return map from sample to edge label
1583 (
1584  const labelList& selectedEdges,
1585  const pointField& samples,
1586  const scalarField& sampleDist,
1587  const scalarField& maxDistSqr,
1588  const scalar minSampleDist
1589 ) const
1590 {
1591  const pointField& surfPoints = surf_.localPoints();
1592  const edgeList& surfEdges = surf_.edges();
1593 
1594  scalar maxSearchSqr = max(maxDistSqr);
1595 
1596  // Define bound box here (gcc-4.8.5)
1597  const treeBoundBox overallBb(samples);
1598 
1600  (
1602  overallBb,
1603  8, // maxLevel
1604  10, // leafsize
1605  3.0 // duplicity
1606  );
1607 
1608  // From patch point to surface edge.
1609  Map<label> nearest(2*selectedEdges.size());
1610 
1611  forAll(selectedEdges, i)
1612  {
1613  label surfEdgeI = selectedEdges[i];
1614 
1615  const edge& e = surfEdges[surfEdgeI];
1616 
1617  if (debug && (i % 1000) == 0)
1618  {
1619  Pout<< "looking at surface feature edge " << surfEdgeI
1620  << " verts:" << e << " points:" << surfPoints[e[0]]
1621  << ' ' << surfPoints[e[1]] << endl;
1622  }
1623 
1624  // Normalized edge vector
1625  vector eVec = e.vec(surfPoints);
1626  scalar eMag = mag(eVec);
1627  eVec /= eMag;
1628 
1629 
1630  //
1631  // Sample along edge
1632  //
1633 
1634  bool exit = false;
1635 
1636  // Coordinate along edge (0 .. eMag)
1637  scalar s = 0.0;
1638 
1639  while (true)
1640  {
1641  point edgePoint(surfPoints[e.start()] + s*eVec);
1642 
1643  pointIndexHit info = ppTree.findNearest
1644  (
1645  edgePoint,
1646  maxSearchSqr
1647  );
1648 
1649  if (!info.hit())
1650  {
1651  // No point close enough to surface edge.
1652  break;
1653  }
1654 
1655  label sampleI = info.index();
1656 
1657  if (info.point().distSqr(edgePoint) < maxDistSqr[sampleI])
1658  {
1659  nearest.insert(sampleI, surfEdgeI);
1660  }
1661 
1662  if (exit)
1663  {
1664  break;
1665  }
1666 
1667  // Step to next sample point using local distance.
1668  // Truncate to max 1/minSampleDist samples per feature edge.
1669  s += max(minSampleDist*eMag, sampleDist[sampleI]);
1670 
1671  if (s >= (1-minSampleDist)*eMag)
1672  {
1673  // Do one more sample, at edge endpoint
1674  s = eMag;
1675  exit = true;
1676  }
1677  }
1678  }
1679 
1680 
1681 
1682  if (debug)
1683  {
1684  // Dump to obj file
1685 
1686  Pout<< "Dumping nearest surface edges to nearestEdges.obj"
1687  << endl;
1688 
1689  OFstream objStream("nearestEdges.obj");
1690 
1691  label vertI = 0;
1692  forAllConstIters(nearest, iter)
1693  {
1694  const label sampleI = iter.key();
1695 
1696  const edge& e = surfEdges[iter.val()];
1697 
1698  meshTools::writeOBJ(objStream, samples[sampleI]); vertI++;
1699 
1700  point nearPt =
1701  e.line(surfPoints).nearestDist(samples[sampleI]).point();
1702 
1703  meshTools::writeOBJ(objStream, nearPt); vertI++;
1704 
1705  objStream<< "l " << vertI-1 << ' ' << vertI << endl;
1706  }
1707  }
1708 
1709  return nearest;
1710 }
1711 
1712 
1713 // Get nearest edge on patch for regularly sampled points along the feature
1714 // edges. Return map from patch edge to feature edge.
1715 //
1716 // Q: using point-based sampleDist and maxDist (distance to look around
1717 // each point). Should they be edge-based e.g. by averaging or max()?
1719 (
1720  const labelList& selectedEdges,
1721  const edgeList& sampleEdges,
1722  const labelList& selectedSampleEdges,
1723  const pointField& samplePoints,
1724  const scalarField& sampleDist,
1725  const scalarField& maxDistSqr,
1726  const scalar minSampleDist
1727 ) const
1728 {
1729  // Build tree out of selected sample edges.
1731  (
1732  treeDataEdge
1733  (
1734  sampleEdges,
1735  samplePoints,
1736  selectedSampleEdges
1737  ),
1738  treeBoundBox(samplePoints), // overall search domain
1739  8, // maxLevel
1740  10, // leafsize
1741  3.0 // duplicity
1742  );
1743 
1744  const pointField& surfPoints = surf_.localPoints();
1745  const edgeList& surfEdges = surf_.edges();
1746 
1747  scalar maxSearchSqr = max(maxDistSqr);
1748 
1749  Map<pointIndexHit> nearest(2*sampleEdges.size());
1750 
1751  //
1752  // Loop over all selected edges. Sample at regular intervals. Find nearest
1753  // sampleEdges (using octree)
1754  //
1755 
1756  forAll(selectedEdges, i)
1757  {
1758  label surfEdgeI = selectedEdges[i];
1759 
1760  const edge& e = surfEdges[surfEdgeI];
1761 
1762  if (debug && (i % 1000) == 0)
1763  {
1764  Pout<< "looking at surface feature edge " << surfEdgeI
1765  << " verts:" << e << " points:" << surfPoints[e[0]]
1766  << ' ' << surfPoints[e[1]] << endl;
1767  }
1768 
1769  // Normalized edge vector
1770  vector eVec = e.vec(surfPoints);
1771  scalar eMag = mag(eVec);
1772  eVec /= eMag;
1773 
1774 
1775  //
1776  // Sample along edge
1777  //
1778 
1779  bool exit = false;
1780 
1781  // Coordinate along edge (0 .. eMag)
1782  scalar s = 0.0;
1783 
1784  while (true)
1785  {
1786  point edgePoint(surfPoints[e.start()] + s*eVec);
1787 
1788  pointIndexHit info = ppTree.findNearest
1789  (
1790  edgePoint,
1791  maxSearchSqr
1792  );
1793 
1794  if (!info.hit())
1795  {
1796  // No edge close enough to surface edge.
1797  break;
1798  }
1799 
1800  label index = info.index();
1801 
1802  label sampleEdgeI = ppTree.shapes().objectIndex(index);
1803 
1804  const edge& e = sampleEdges[sampleEdgeI];
1805 
1806  if (info.point().distSqr(edgePoint) < maxDistSqr[e.start()])
1807  {
1808  nearest.insert
1809  (
1810  sampleEdgeI,
1811  pointIndexHit(true, edgePoint, surfEdgeI)
1812  );
1813  }
1814 
1815  if (exit)
1816  {
1817  break;
1818  }
1819 
1820  // Step to next sample point using local distance.
1821  // Truncate to max 1/minSampleDist samples per feature edge.
1822  // s += max(minSampleDist*eMag, sampleDist[e.start()]);
1823  s += 0.01*eMag;
1824 
1825  if (s >= (1-minSampleDist)*eMag)
1826  {
1827  // Do one more sample, at edge endpoint
1828  s = eMag;
1829  exit = true;
1830  }
1831  }
1832  }
1833 
1834 
1835  if (debug)
1836  {
1837  // Dump to obj file
1838 
1839  Pout<< "Dumping nearest surface feature edges to nearestEdges.obj"
1840  << endl;
1841 
1842  OFstream objStream("nearestEdges.obj");
1843 
1844  label vertI = 0;
1845  forAllConstIters(nearest, iter)
1846  {
1847  const label sampleEdgeI = iter.key();
1848 
1849  const edge& sampleEdge = sampleEdges[sampleEdgeI];
1850 
1851  // Write line from edgeMid to point on feature edge
1852  meshTools::writeOBJ(objStream, sampleEdge.centre(samplePoints));
1853  vertI++;
1854 
1855  meshTools::writeOBJ(objStream, iter.val().point());
1856  vertI++;
1857 
1858  objStream<< "l " << vertI-1 << ' ' << vertI << endl;
1859  }
1860  }
1861 
1862  return nearest;
1863 }
1864 
1865 
1866 // Get nearest surface edge for every sample. Return in form of
1867 // labelLists giving surfaceEdge label&intersectionpoint.
1869 (
1870  const labelList& selectedEdges,
1871  const pointField& samples,
1872  scalar searchSpanSqr, // Search span
1873  labelList& edgeLabel,
1874  labelList& edgeEndPoint,
1875  pointField& edgePoint
1876 ) const
1877 {
1878  edgeLabel.setSize(samples.size());
1879  edgeEndPoint.setSize(samples.size());
1880  edgePoint.setSize(samples.size());
1881 
1882  const pointField& localPoints = surf_.localPoints();
1883 
1884  treeBoundBox searchDomain(localPoints);
1885  searchDomain.inflate(0.1);
1886 
1888  (
1889  treeDataEdge
1890  (
1891  surf_.edges(),
1892  localPoints,
1893  selectedEdges
1894  ),
1895  searchDomain, // overall search domain
1896  8, // maxLevel
1897  10, // leafsize
1898  3.0 // duplicity
1899  );
1900  const auto& treeData = ppTree.shapes();
1901 
1902  forAll(samples, i)
1903  {
1904  const point& sample = samples[i];
1905 
1906  pointIndexHit info = ppTree.findNearest
1907  (
1908  sample,
1909  searchSpanSqr
1910  );
1911 
1912  if (!info.hit())
1913  {
1914  edgeLabel[i] = -1;
1915  }
1916  else
1917  {
1918  // Need to recalculate to classify edgeEndPoint
1919  pointIndexHit pHit = edgeNearest
1920  (
1921  treeData.line(info.index()),
1922  sample
1923  );
1924 
1925  edgeLabel[i] = treeData.objectIndex(info.index());
1926  edgePoint[i] = pHit.point();
1927  edgeEndPoint[i] = pHit.index();
1928  }
1929  }
1930 }
1931 
1932 
1933 // Get nearest point on nearest feature edge for every sample (is edge)
1935 (
1936  const labelList& selectedEdges,
1937 
1938  const edgeList& sampleEdges,
1939  const labelList& selectedSampleEdges,
1940  const pointField& samplePoints,
1941 
1942  const vector& searchSpan, // Search span
1943  labelList& edgeLabel, // label of surface edge or -1
1944  pointField& pointOnEdge, // point on above edge
1945  pointField& pointOnFeature // point on sample edge
1946 ) const
1947 {
1948  edgeLabel.setSize(selectedSampleEdges.size());
1949  pointOnEdge.setSize(selectedSampleEdges.size());
1950  pointOnFeature.setSize(selectedSampleEdges.size());
1951 
1952  treeBoundBox searchDomain(surf_.localPoints());
1953 
1955  (
1956  treeDataEdge
1957  (
1958  surf_.edges(),
1959  surf_.localPoints(),
1960  selectedEdges
1961  ),
1962  searchDomain, // overall search domain
1963  8, // maxLevel
1964  10, // leafsize
1965  3.0 // duplicity
1966  );
1967  const auto& treeData = ppTree.shapes();
1968 
1969  forAll(selectedSampleEdges, i)
1970  {
1971  const edge& e = sampleEdges[selectedSampleEdges[i]];
1972 
1973  linePointRef edgeLine = e.line(samplePoints);
1974 
1975  point eMid(edgeLine.centre());
1976 
1977  treeBoundBox tightest(eMid - searchSpan, eMid + searchSpan);
1978 
1979  pointIndexHit info = ppTree.findNearest
1980  (
1981  edgeLine,
1982  tightest,
1983  pointOnEdge[i]
1984  );
1985 
1986  if (!info.hit())
1987  {
1988  edgeLabel[i] = -1;
1989  }
1990  else
1991  {
1992  edgeLabel[i] = treeData.objectIndex(info.index());
1993  pointOnFeature[i] = info.point();
1994  }
1995  }
1996 }
1997 
1998 
2000 (
2001  const edgeList& edges,
2002  const pointField& points,
2003  scalar searchSpanSqr, // Search span
2004  labelList& edgeLabel
2005 ) const
2006 {
2007  edgeLabel = labelList(surf_.nEdges(), -1);
2008 
2009  treeBoundBox searchDomain(points);
2010  searchDomain.inflate(0.1);
2011 
2013  (
2014  treeDataEdge(edges, points), // All edges
2015 
2016  searchDomain, // overall search domain
2017  8, // maxLevel
2018  10, // leafsize
2019  3.0 // duplicity
2020  );
2021  const auto& treeData = ppTree.shapes();
2022 
2023  const edgeList& surfEdges = surf_.edges();
2024  const pointField& surfLocalPoints = surf_.localPoints();
2025 
2026  forAll(surfEdges, edgeI)
2027  {
2028  const edge& sample = surfEdges[edgeI];
2029 
2030  const point& startPoint = surfLocalPoints[sample.start()];
2031  const point& midPoint = sample.centre(surfLocalPoints);
2032 
2033  pointIndexHit infoMid = ppTree.findNearest
2034  (
2035  midPoint,
2036  searchSpanSqr
2037  );
2038 
2039  if (infoMid.hit())
2040  {
2041  const vector surfEdgeDir = midPoint - startPoint;
2042 
2043  const vector featEdgeDir = treeData.line(infoMid.index()).vec();
2044 
2045  // Check that the edges are nearly parallel
2046  if (mag(surfEdgeDir ^ featEdgeDir) < parallelTolerance)
2047  {
2048  edgeLabel[edgeI] = edgeI;
2049  }
2050  }
2051  }
2052 }
2053 
2054 
2055 // * * * * * * * * * * * * * * * Member Operators * * * * * * * * * * * * * //
2058 {
2059  if (this == &rhs)
2060  {
2061  return; // Self-assignment is a no-op
2062  }
2063 
2064  if (&surf_ != &rhs.surface())
2065  {
2067  << "Operating on different surfaces"
2068  << abort(FatalError);
2069  }
2070 
2071  featurePoints_ = rhs.featurePoints();
2072  featureEdges_ = rhs.featureEdges();
2073  externalStart_ = rhs.externalStart();
2074  internalStart_ = rhs.internalStart();
2075 }
2076 
2077 
2078 // ************************************************************************* //
List< ReturnType > get(const UPtrList< T > &list, const AccessOp &aop)
List of values generated by applying the access operation to each list item.
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
A line primitive.
Definition: line.H:52
A class for handling file names.
Definition: fileName.H:72
Holds (reference to) pointField. Encapsulation of data needed for octree searches. Used for searching for nearest point. No bounding boxes around points. Only overlaps and calcNearest are implemented, rest makes little sense.
Definition: treeDataPoint.H:58
void deleteBox(List< edgeStatus > &edgeStat, const treeBoundBox &bb, const bool removeInside) const
Mark edge status as &#39;NONE&#39; for edges inside/outside box.
scalar mag(const UList< point > &pts) const
The length (L2-norm) of the edge vector.
Definition: edgeI.H:419
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
labelList pointLabels(nPoints, -1)
void write(const fileName &fName) const
Write as dictionary to file.
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:129
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:608
void append(const T &val)
Append an element at the end of the list.
Definition: List.H:521
const Type & shapes() const noexcept
Reference to shape.
List< edge > edgeList
List of edge.
Definition: edgeList.H:32
virtual const fileName & name() const override
Read/write access to the name of the stream.
Definition: OSstream.H:134
A 1D array of objects of type <T>, where the size of the vector is known and used for subscript bound...
Definition: BitOps.H:56
void inflate(const scalar factor)
Expand box by factor*mag(span) in all dimensions.
Definition: boundBoxI.H:381
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
dimensionedSymmTensor sqr(const dimensionedVector &dv)
Unit conversion functions.
Output to file stream as an OSstream, normally using std::ofstream for the actual output...
Definition: OFstream.H:71
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
scalarField samples(nIntervals, Zero)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
PointIndexHit< point > pointIndexHit
A PointIndexHit with a 3D point.
Definition: pointIndexHit.H:58
Holds data for octree to work on an edges subset.
Definition: treeDataEdge.H:53
label externalStart() const
Start of external edges.
Map< label > nearestSamples(const labelList &selectedPoints, const pointField &samples, const scalarField &maxDistSqr) const
Find nearest sample for selected surface points.
void subsetBox(List< edgeStatus > &edgeStat, const treeBoundBox &bb) const
Mark edge status outside box as &#39;NONE&#39;.
Map< pointIndexHit > nearestEdges(const labelList &selectedEdges, const edgeList &sampleEdges, const labelList &selectedSampleEdges, const pointField &samplePoints, const scalarField &sampleDist, const scalarField &maxDistSqr, const scalar minSampleDist=0.1) const
Like nearestSamples but now gets nearest point on.
void setCapacity(const label len)
Alter the size of the underlying storage.
Definition: DynamicListI.H:303
This class describes the interaction of an object (often a face) and a point. It carries the info of ...
Definition: pointIndexHit.H:44
entry * add(entry *entryPtr, bool mergeEntry=false)
Add a new entry.
Definition: dictionary.C:625
void writeOBJ(Ostream &os, const point &pt)
Write obj representation of a point.
Definition: meshTools.C:196
const labelList & featureEdges() const
Return feature edge list.
Geometric class that creates a 3D plane and can return the intersection point between a line and the ...
Definition: plane.H:90
label objectIndex(const label index) const
Map from shape index to original (non-subset) edge label.
Definition: treeDataEdge.H:388
Lookup type of boundary radiation properties.
Definition: lookup.H:57
List< labelList > labelListList
List of labelList.
Definition: labelList.H:38
void writeObj(const fileName &prefix) const
Write to separate OBJ files (region, external, internal edges,.
scalar lineIntersect(const line< PointType, PointRef > &l) const
Return the cutting point between the plane and a line passing through the supplied points...
Definition: plane.H:317
labelList selectFeatureEdges(const bool regionEdges, const bool externalEdges, const bool internalEdges) const
Helper function: select a subset of featureEdges_.
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
void excludeNonManifold(List< edgeStatus > &edgeStat) const
Mark edges with >2 connected faces as &#39;NONE&#39;.
List< edgeStatus > toStatus() const
From member feature edges to status per edge.
bool insert(const edge &key, const T &obj)
Copy insert a new entry, not overwriting existing entries.
Definition: HashTableI.H:152
label start() const noexcept
The start (first) vertex label.
Definition: edge.H:147
bool readEntry(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX, IOobjectOption::readOption readOpt=IOobjectOption::MUST_READ) const
Find entry and assign to T val. FatalIOError if it is found and the number of tokens is incorrect...
Not a classified feature edge.
unsigned int count(const UList< bool > &bools, const bool val=true)
Count number of &#39;true&#39; entries.
Definition: BitOps.H:73
const point_type & point() const noexcept
Return point, no checks.
labelList trimFeatures(const scalar minLen, const label minElems, const scalar includedAngle)
Delete small sets of edges. Edges are stringed up and any.
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:38
const dimensionedScalar e
Elementary charge.
Definition: createFields.H:11
void setSize(const label n)
Alias for resize()
Definition: List.H:320
dimensionedScalar cos(const dimensionedScalar &ds)
line< point, const point & > linePointRef
A line using referred points.
Definition: line.H:66
const pointField & points
A 1D vector of objects of type <T> that resizes itself as necessary to accept the new objects...
Definition: DynamicList.H:51
Mid-point interpolation (weighting factors = 0.5) scheme class.
Definition: midPoint.H:51
An edge is a list of two vertex labels. This can correspond to a directed graph edge or an edge on a ...
Definition: edge.H:59
const dimensionedScalar b
Wien displacement law constant: default SI units: [m.K].
Definition: createFields.H:27
void setFromStatus(const List< edgeStatus > &edgeStat, const scalar includedAngle)
Set from status per edge.
void operator=(const surfaceFeatures &rhs)
void writeStats(Ostream &os) const
Write some information.
const triSurface & surface() const
void excludeBox(List< edgeStatus > &edgeStat, const treeBoundBox &bb) const
Mark edge status inside box as &#39;NONE&#39;.
const labelListList & edgeFaces() const
Return edge-face addressing.
const edgeList & edges() const
Return list of edges, address into LOCAL point list.
Vector< scalar > vector
Definition: vector.H:57
void append(const T &val)
Copy append an element to the end of this list.
Definition: DynamicList.H:584
const treeBoundBox & bb() const
Top bounding box.
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:26
label index() const noexcept
Return the hit index.
errorManip< error > abort(error &err)
Definition: errorManip.H:139
const wordList surface
Standard surface field types (scalar, vector, tensor, etc)
bool contains(const vector &dir, const point &) const
Contains point (inside or on edge) and moving in direction.
Definition: treeBoundBox.C:413
An Ostream is an abstract base class for all output systems (streams, files, token lists...
Definition: Ostream.H:56
dimensionedScalar sin(const dimensionedScalar &ds)
label nEdges() const
Number of edges in patch.
A Vector of values with scalar precision, where scalar is float/double depending on the compilation f...
int debug
Static debugging option.
const labelList & featurePoints() const
Return feature point list.
DynamicList< T, SizeMin > & shrink()
Calls shrink_to_fit() and returns a reference to the DynamicList.
Definition: DynamicListI.H:447
OBJstream os(runTime.globalPath()/outputName)
defineTypeNameAndDebug(combustionModel, 0)
Input from file stream as an ISstream, normally using std::ifstream for the actual input...
Definition: IFstream.H:51
const Vector< Cmpt > & centre(const Foam::UList< Vector< Cmpt >> &) const noexcept
Return this (for point which is a typedef to Vector<scalar>)
Definition: VectorI.H:67
messageStream Warning
Warning stream (stdout output on master, null elsewhere), with additional &#39;FOAM Warning&#39; header text...
Point centre() const
Return centre (centroid)
Definition: lineI.H:83
void subsetPlane(List< edgeStatus > &edgeStat, const plane &cutPlane) const
If edge does not intersect the plane, mark as &#39;NONE&#39;.
void write(Ostream &os, const bool subDict=true) const
Write dictionary, normally with sub-dictionary formatting.
Definition: dictionaryIO.C:204
bool hit() const noexcept
Is there a hit?
vector point
Point is a vector.
Definition: point.H:37
Non-pointer based hierarchical recursive searching.
void findFeatures(const scalar includedAngle, const bool geometricTestOnly)
Find feature edges using provided included angle.
void nearestSurfEdge(const labelList &selectedEdges, const pointField &samples, scalar searchSpanSqr, labelList &edgeLabel, labelList &edgeEndPoint, pointField &edgePoint) const
Find nearest surface edge (out of selectedEdges) for.
surfaceFeatures(const triSurface &surf)
Construct from surface.
void excludeOpen(List< edgeStatus > &edgeStat) const
Mark edges with a single connected face as &#39;NONE&#39;.
void nearestFeatEdge(const edgeList &edges, const pointField &points, scalar searchSpanSqr, labelList &edgeLabel) const
Find nearest feature edge to each surface edge. Uses the.
point centre(const UList< point > &pts) const
Return centre point (centroid) of the edge.
Definition: edgeI.H:372
Standard boundBox with extra functionality for use in octree.
Definition: treeBoundBox.H:90
void writeDict(Ostream &os) const
Write as dictionary.
label n
Field< vector > vectorField
Specialisation of Field<T> for vector.
List< label > labelList
A List of labels.
Definition: List.H:62
volScalarField & p
label internalStart() const
Start of internal edges.
Triangulated surface description with patch information.
Definition: triSurface.H:71
gmvFile<< "tracers "<< particles.size()<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().x()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().y()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.timeName(), cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
constexpr scalar degToRad(const scalar deg) noexcept
Conversion from degrees to radians.
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
const volScalarField & p0
Definition: EEqn.H:36
Holds feature edges/points of surface.
PointHit< point > pointHit
A PointHit with a 3D point.
Definition: pointHit.H:43
Namespace for OpenFOAM.
forAllConstIters(mixture.phases(), phase)
Definition: pEqn.H:28
A HashTable to objects of type <T> with a label key.
const pointField & pts