1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | foam-extend: Open Source CFD
4 \\ / O peration | Version: 3.2
5 \\ / A nd | Web: http://www.foam-extend.org
6 \\/ M anipulation | For copyright notice see file Copyright
7 -------------------------------------------------------------------------------
9 This file is part of foam-extend.
11 foam-extend is free software: you can redistribute it and/or modify it
12 under the terms of the GNU General Public License as published by the
13 Free Software Foundation, either version 3 of the License, or (at your
14 option) any later version.
16 foam-extend is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with foam-extend. If not, see <http://www.gnu.org/licenses/>.
25 Mass-conservative face interpolation of face data between two
29 Hrvoje Jasak, Wikki Ltd. All rights reserved
32 Martin Beaudoin, Hydro-Quebec, (2008)
34 \*---------------------------------------------------------------------------*/
36 #include "GGIInterpolationTemplate.H"
37 #include "objectHit.H"
39 #include "DynamicList.H"
40 #include "dimensionedConstants.H"
42 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
47 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
49 template<class MasterPatch, class SlavePatch>
50 const Foam::debug::tolerancesSwitch
51 GGIInterpolation<MasterPatch, SlavePatch>::areaErrorTol_
55 "Minimum GGI face to face intersection area. The smallest accepted GGI weighting factor."
58 template<class MasterPatch, class SlavePatch>
59 const Foam::debug::tolerancesSwitch
60 GGIInterpolation<MasterPatch, SlavePatch>::featureCosTol_
64 "Minimum cosine value between 2 GGI patch neighbouring facet normals."
67 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
69 template<class MasterPatch, class SlavePatch>
70 void GGIInterpolation<MasterPatch, SlavePatch>::calcAddressing() const
78 || uncoveredMasterAddrPtr_
79 || uncoveredSlaveAddrPtr_
84 "void GGIInterpolation<MasterPatch, SlavePatch>::"
85 "calcAddressing() const"
86 ) << "Addressing already calculated"
94 "void GGIInterpolation<MasterPatch, SlavePatch>::"
95 "calcAddressing() const"
96 ) << "Evaluation of GGI weighting factors:" << endl;
99 // Create the dynamic lists to hold the addressing
101 // The final master/slave list, after filtering out the "false" neighbours
102 List<DynamicList<label, 8> > masterNeighbors(masterPatch_.size());
103 List<DynamicList<label, 8> > slaveNeighbors(slavePatch_.size());
106 List<DynamicList<scalar, 8> > masterNeighborsWeights(masterPatch_.size());
107 List<DynamicList<scalar, 8> > slaveNeighborsWeights(slavePatch_.size());
109 // First, find a rough estimate of each slave and master facet
110 // neighborhood by filtering out all the faces located outside of
111 // an Axis-Aligned Bounding Box (AABB). Warning: This algorithm
112 // is based on the evaluation of AABB boxes, which is pretty fast;
113 // but still the complexity of the algorithm is n^2, wich is
114 // pretty bad for GGI patches composed of 100,000 of facets... So
115 // here is the place where we could certainly gain major speedup
116 // for larger meshes.
118 // The candidates master neighbours
119 // Choice of algorithm:
120 // 1) Axis-aligned bounding box
121 // 2) Octree search with bounding box
122 // 3) 3-D vector distance
123 // 4) n-Squared search
124 labelListList candidateMasterNeighbors;
128 findNeighboursAABB(candidateMasterNeighbors);
130 else if (reject_ == BB_OCTREE)
132 findNeighboursBBOctree(candidateMasterNeighbors);
134 else if (reject_ == THREE_D_DISTANCE)
136 findNeighbours3D(candidateMasterNeighbors);
138 else if (reject_ == N_SQUARED)
140 candidateMasterNeighbors.setSize(masterPatch_.size());
142 // Mark N-squared search
143 labelList nSquaredList(slavePatch_.size());
144 forAll (nSquaredList, i)
149 forAll (candidateMasterNeighbors, j)
151 candidateMasterNeighbors[j] = nSquaredList;
155 // Next, we move to the 2D world. We project each slave and
156 // master face onto a local plane defined by the master face
157 // normal. We filter out a few false neighbors using the
158 // Separating Axes Theorem
160 // It is in this local plane that we will refine our list of
161 // neighbors. So for a given a neighbor face, we need as many
162 // projections as there are neighbors closeby.
164 const pointField& masterPatchPoints = masterPatch_.points();
165 const vectorField masterPatchNormals = masterPatch_.faceNormals();
167 // Store the polygon made by projecting the face points onto the
169 // The master faces polygons
170 List<pointField> masterFace2DPolygon(masterPatch_.size());
172 // Tolerance factor for the Separation of Axes Theorem == distErrorTol_
174 forAll (masterPatch_, faceMi)
176 // First, we make sure that all the master faces points are
177 // recomputed onto the 2D plane defined by the master faces
179 // For triangles, this is useless, but for N-gons
180 // with more than 3 points, this is essential.
181 // The intersection between the master and slave faces will be
182 // done in these 2D reference frames
184 // A few basic information to keep close-by
185 vector currentMasterFaceNormal = masterPatchNormals[faceMi];
186 vector currentMasterFaceCentre =
187 masterPatch_[faceMi].centre(masterPatchPoints);
189 scalarField facePolygonErrorProjection;
191 // Project the master faces points onto the normal face plane to
192 // form a flattened polygon
193 masterFace2DPolygon[faceMi] =
196 masterPatch_[faceMi].points(masterPatchPoints),
197 currentMasterFaceCentre,
198 currentMasterFaceNormal,
199 facePolygonErrorProjection
202 // Next we compute an orthonormal basis (u, v, w) aligned with
203 // the face normal for doing the 3D to 2D projection.
205 // "w" is aligned on the face normal. We need to select a "u"
206 // direction, it can be anything as long as it lays on the
207 // projection plane. We chose to use the direction from the
208 // master face center to the most distant projected master face
209 // point on the plane. Finally, we get "v" by evaluating the
210 // cross-product w^u = v. And we make sure that u, v, and w are
214 // u = vector from face center to most distant projected master face point.
216 // ^y / | . .w = normal to master face
231 orthoNormalBasis uvw =
232 computeOrthonormalBasis
234 currentMasterFaceCentre,
235 currentMasterFaceNormal,
236 masterFace2DPolygon[faceMi]
239 // Recompute the master polygon into this orthoNormalBasis
240 // We should only see a rotation along the normal of the face here
241 List<point2D> masterPointsInUV;
242 scalarField masterErrorProjectionAlongW;
248 currentMasterFaceCentre,
249 masterFace2DPolygon[faceMi],
250 masterErrorProjectionAlongW // Should be at zero all the way
253 // Compute the surface area of the polygon;
254 // We need this for computing the weighting factors
255 scalar surfaceAreaMasterPointsInUV = area2D(masterPointsInUV);
257 // Check if polygon is CW.. Should not, it should be CCW; but
258 // better and cheaper to check here
259 if (surfaceAreaMasterPointsInUV < 0.0)
261 reverse(masterPointsInUV);
262 surfaceAreaMasterPointsInUV = -surfaceAreaMasterPointsInUV;
264 // Just generate a warning until we can verify this is a non issue
267 "void GGIInterpolation<MasterPatch, SlavePatch>::"
269 ) << "The master projected polygon was CW instead of CCW. "
270 << "This is strange..." << endl;
273 // Next, project the candidate master neighbours faces points
274 // onto the same plane using the new orthonormal basis
275 const labelList& curCMN = candidateMasterNeighbors[faceMi];
277 forAll (curCMN, neighbI)
279 // For each points, compute the dot product with u,v,w. The
280 // [u,v] component will gives us the 2D cordinates we are
281 // looking for for doing the 2D intersection The w component
282 // is basically the projection error normal to the projection
285 // NB: this polygon is most certainly CW w/r to the uvw
286 // axis because of the way the normals are oriented on
287 // each side of the GGI interface... We will switch the
288 // polygon to CCW in due time...
289 List<point2D> neighbPointsInUV;
290 scalarField neighbErrorProjectionAlongW;
292 // We use the xyz points directly, with a possible transformation
293 pointField curSlaveFacePoints =
294 slavePatch_[curCMN[neighbI]].points(slavePatch_.points());
298 // Transform points to master plane
299 if (forwardT_.size() == 1)
313 forwardT_[curCMN[neighbI]],
319 // Apply the translation offset in order to keep the
320 // neighbErrorProjectionAlongW values to a minimum
323 if (forwardSep_.size() == 1)
325 curSlaveFacePoints += forwardSep_[0];
329 curSlaveFacePoints += forwardSep_[curCMN[neighbI]];
337 currentMasterFaceCentre,
339 neighbErrorProjectionAlongW
342 // We are now ready to filter out the "bad" neighbours.
343 // For this, we will apply the Separating Axes Theorem
344 // http://en.wikipedia.org/wiki/Separating_axis_theorem.
346 // This will be the second and last quick reject test.
347 // We will use the 2D projected points for both the master
348 // patch and its neighbour candidates
351 detect2dPolygonsOverlap
355 sqrt(areaErrorTol_()) // distErrorTol
359 // We have an overlap between the master face and this
361 label faceMaster = faceMi;
362 label faceSlave = curCMN[neighbI];
364 // Compute the surface area of the neighbour polygon;
365 // We need this for computing the weighting factors
366 scalar surfaceAreaNeighbPointsInUV = area2D(neighbPointsInUV);
368 // Check for CW polygons. It most certainly is, and
369 // the polygon intersection algorithms are expecting
370 // to work with CCW point ordering for the polygons
371 if (surfaceAreaNeighbPointsInUV < 0.0)
373 reverse(neighbPointsInUV);
374 surfaceAreaNeighbPointsInUV = -surfaceAreaNeighbPointsInUV;
378 // We compute the intersection area using the
379 // Sutherland-Hodgman algorithm. Of course, if the
380 // intersection area is 0, that would constitute the last and
381 // final reject test, but it would also be an indication that
382 // our 2 previous rejection tests are a bit laxed... or that
383 // maybe we are in presence of concave polygons....
384 scalar intersectionArea =
391 if (intersectionArea > VSMALL) // Or > areaErrorTol_ ???
393 // We compute the GGI weights based on this
394 // intersection area, and on the individual face
395 // area on each side of the GGI.
397 // Since all the intersection have been computed
398 // in the projected UV space we need to compute
399 // the weights using the surface area from the
400 // faces projection as well. That way, we make
401 // sure all our factors will sum up to 1.0.
403 masterNeighbors[faceMaster].append(faceSlave);
404 slaveNeighbors[faceSlave].append(faceMaster);
406 masterNeighborsWeights[faceMaster].append
408 intersectionArea/surfaceAreaMasterPointsInUV
411 slaveNeighborsWeights[faceSlave].append
413 intersectionArea/surfaceAreaNeighbPointsInUV
420 "GGIInterpolation<MasterPatch, SlavePatch>::"
422 ) << "polygonIntersection is returning a "
423 << "zero surface area between " << nl
424 << " Master face: " << faceMi
425 << " and Neighbour face: " << curCMN[neighbI]
426 << " intersection area = " << intersectionArea << nl
427 << "Please check the two quick-check algorithms for "
428 << "GGIInterpolation. Something is missing." << endl;
433 // We went through all the possible neighbors for this face.
437 // Allocate the member attributes and pack addressing
438 masterAddrPtr_ = new labelListList(masterPatch_.size());
439 labelListList& ma = *masterAddrPtr_;
441 masterWeightsPtr_ = new scalarListList(masterPatch_.size());
442 scalarListList& maW = *masterWeightsPtr_;
446 ma[mfI].transfer(masterNeighbors[mfI].shrink());
447 maW[mfI].transfer(masterNeighborsWeights[mfI].shrink());
450 slaveAddrPtr_ = new labelListList(slavePatch_.size());
451 labelListList& sa = *slaveAddrPtr_;
453 slaveWeightsPtr_ = new scalarListList(slavePatch_.size());
454 scalarListList& saW = *slaveWeightsPtr_;
458 sa[sfI].transfer(slaveNeighbors[sfI].shrink());
459 saW[sfI].transfer(slaveNeighborsWeights[sfI].shrink());
462 // Now that the neighbourhood is known, let's go hunting for
463 // non-overlapping faces
464 uncoveredMasterAddrPtr_ =
467 findNonOverlappingFaces(maW, masterNonOverlapFaceTol_)
470 uncoveredSlaveAddrPtr_ =
473 findNonOverlappingFaces(saW, slaveNonOverlapFaceTol_)
476 // Rescaling the weighting factors so they will sum up to 1.0
477 // See the comment for the method ::rescaleWeightingFactors() for
478 // more information. By default, we always rescale. But for some
479 // special kind of GGI interpolation, like the mixingPlaneGGI,
480 // then we need the brute values, so no rescaling in that
481 // case. Hence the little flag rescaleGGIWeightingFactors_
483 if (rescaleGGIWeightingFactors_)
485 rescaleWeightingFactors();
490 // Rescaling the weighting factors so they will sum up to 1.0 This is
491 // necessary for the slave weighting factors because intersection with
492 // master neighbours are usually computed from different projection
493 // plane, so the weighting factor don't quite sum up to 1.0 For the
494 // slave weighting factors, we are usually talking of delta of the
495 // order of 10e-6 here.
497 // For the master weighting factor, this is another story. We truly
498 // expect that the master weighting factors will exactly sum up to 1.0
499 // if all the neighbours are properly identified.
501 // However, for concentric circular geometry, if the circumferantial
502 // resolution is too coarse, we will end up with some part of the face
503 // surface that are not taken into account because they do not
504 // physically overlap any neighbours. For example, think of 2
505 // concentric circular patches, slightly rotated one relatively to the
506 // other. A good case: the ercoftac conical diffuser, Case0... GGI
507 // located right between the cylindrical and conical parts, rotate the
508 // cylindrical by 15 degrees. For this case, we will need to devise a
509 // decent strategy in order to intelligently take care of these
512 // The purpose of the ::rescaleWeightingFactors() method is mainly for
514 template<class MasterPatch, class SlavePatch>
515 void GGIInterpolation<MasterPatch, SlavePatch>::rescaleWeightingFactors() const
517 scalarListList& maW = *masterWeightsPtr_;
518 scalarListList& saW = *slaveWeightsPtr_;
520 // Memorize the largest correction needed in order to provide some
521 // basic info to the user
522 scalar largestSWC = 0;
526 scalar largestMWC = 0;
530 // Rescaling the slave weights
535 uncoveredMasterFaces().size() > 0
536 || uncoveredSlaveFaces().size() > 0
541 "void GGIInterpolation<MasterPatch, SlavePatch>::"
542 "rescaleWeightingFactors() const"
543 ) << "Uncovered faces found. On master: "
544 << uncoveredMasterFaces().size()
545 << " on slave: " << uncoveredSlaveFaces().size() << endl;
551 scalar slaveWeightSum = Foam::sum(saW[saWi]);
553 if (saW[saWi].size() > 0)
555 saW[saWi] = saW[saWi]/slaveWeightSum;
558 curSWC = mag(1.0 - slaveWeightSum);
559 largestSWC = Foam::max(largestSWC, curSWC);
565 // Rescaling the master weights
568 scalar masterWeightSum = Foam::sum(maW[maWi]);
570 if (maW[maWi].size() > 0)
572 maW[maWi] = maW[maWi]/masterWeightSum;
575 curMWC = mag(1.0 - masterWeightSum);
576 largestMWC = Foam::max(largestMWC, curMWC);
584 if (saW.size() > 0 && maW.size() > 0)
586 Info<< " Largest slave weighting factor correction : "
588 << " average: " << sumSWC/saW.size() << nl
589 << " Largest master weighting factor correction: "
591 << " average: " << sumMWC/maW.size() << endl;
597 // Find non-overlapping faces from both master and slave patches
598 // The default non-overlapping criteria is total absence of neighbours.
599 // Later on, ths criteria will be based on minimum surface intersection, or
600 // minimum weight factor
601 template<class MasterPatch, class SlavePatch>
603 GGIInterpolation<MasterPatch, SlavePatch>::findNonOverlappingFaces
605 const scalarListList& patchWeights,
606 const scalar& nonOverlapFaceTol // = min sum of the neighbour weights
609 tmp<labelField> tpatchFaceNonOverlapAddr(new labelField());
610 labelField& patchFaceNonOverlapAddr = tpatchFaceNonOverlapAddr();
612 DynamicList<label, 64> patchFaceNonOverlap(patchWeights.size());
614 // Scan the list of patch weights, looking for empty lists
615 forAll (patchWeights, paWi)
617 scalar sumWeightsFace = sum(patchWeights[paWi]);
619 if (sumWeightsFace <= nonOverlapFaceTol)
621 // Store local index.
622 patchFaceNonOverlap.append(paWi);
626 if (patchFaceNonOverlap.size() > 0)
628 patchFaceNonOverlapAddr.transfer(patchFaceNonOverlap.shrink());
633 InfoIn("GGIInterpolation::findNonOverlappingFaces")
634 << " : Found " << patchFaceNonOverlapAddr.size()
635 << " non-overlapping faces for this GGI patch" << endl;
638 return tpatchFaceNonOverlapAddr;
641 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
643 } // End namespace Foam
645 // ************************************************************************* //