openfoam/applications/utilities/surface/surfaceBooleanFeatures/surfaceBooleanFeatures.C
2020-04-21 14:59:07 +02:00

1804 lines
47 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2016 OpenFOAM Foundation
Copyright (C) 2016-2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Application
surfaceBooleanFeatures
Group
grpSurfaceUtilities
Description
Generates the extendedFeatureEdgeMesh for the interface between a boolean
operation on two surfaces.
Assumes that the orientation of the surfaces is correct:
- if the operation is union or intersection, that both surface's normals
(n) have the same orientation with respect to a point, i.e. surfaces
A and B are orientated the same with respect to point x:
\verbatim
_______
| |--> n
| ___|___ x
|A | | |--> n
|___|___| B|
| |
|_______|
\endverbatim
- if the operation is a subtraction, the surfaces should be oppositely
oriented with respect to a point, i.e. for (A - B), then B's orientation
should be such that x is "inside", and A's orientation such that x is
"outside"
\verbatim
_______
| |--> n
| ___|___ x
|A | | |
|___|___| B|
| n <--|
|_______|
\endverbatim
When the operation is peformed - for union, all of the edges generates where
one surfaces cuts another are all "internal" for union, and "external" for
intersection, (B - A) and (A - B).
This has been assumed, formal (dis)proof is invited.
\*---------------------------------------------------------------------------*/
#include "triSurface.H"
#include "argList.H"
#include "Time.H"
#include "featureEdgeMesh.H"
#include "extendedFeatureEdgeMesh.H"
#include "triSurfaceSearch.H"
#include "triSurfaceMesh.H"
#include "OFstream.H"
#include "OBJstream.H"
#include "booleanSurface.H"
#include "edgeIntersections.H"
#include "meshTools.H"
#include "DynamicField.H"
#include "Enum.H"
#ifndef NO_CGAL
#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_traits.h>
#include <CGAL/AABB_face_graph_triangle_primitive.h>
#include "CGALIndexedPolyhedron.H"
#include "PolyhedronReader.H"
typedef CGAL::AABB_face_graph_triangle_primitive
<
Polyhedron, CGAL::Default, CGAL::Tag_false
> Primitive;
typedef CGAL::AABB_traits<K, Primitive> Traits;
typedef CGAL::AABB_tree<Traits> Tree;
typedef boost::optional
<
Tree::Intersection_and_primitive_id<Segment>::Type
> Segment_intersection;
#endif // NO_CGAL
using namespace Foam;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
bool intersectSurfaces
(
triSurface& surf,
edgeIntersections& edgeCuts
)
{
bool hasMoved = false;
for (label iter = 0; iter < 10; iter++)
{
Info<< "Determining intersections of surface edges with itself" << endl;
// Determine surface edge intersections. Allow surface to be moved.
// Number of iterations needed to resolve degenerates
label nIters = 0;
{
triSurfaceSearch querySurf(surf);
scalarField surfPointTol
(
max(1e-3*edgeIntersections::minEdgeLength(surf), SMALL)
);
// Determine raw intersections
edgeCuts = edgeIntersections
(
surf,
querySurf,
surfPointTol
);
// Shuffle a bit to resolve degenerate edge-face hits
{
pointField points(surf.points());
nIters =
edgeCuts.removeDegenerates
(
5, // max iterations
surf,
querySurf,
surfPointTol,
points // work array
);
if (nIters != 0)
{
// Update geometric quantities
surf.movePoints(points);
hasMoved = true;
}
}
}
}
if (hasMoved)
{
fileName newFile("surf.obj");
Info<< "Surface has been moved. Writing to " << newFile << endl;
surf.write(newFile);
}
return hasMoved;
}
// Keep on shuffling surface points until no more degenerate intersections.
// Moves both surfaces and updates set of edge cuts.
bool intersectSurfaces
(
triSurface& surf1,
edgeIntersections& edgeCuts1,
triSurface& surf2,
edgeIntersections& edgeCuts2
)
{
bool hasMoved1 = false;
bool hasMoved2 = false;
for (label iter = 0; iter < 10; iter++)
{
Info<< "Determining intersections of surf1 edges with surf2"
<< " faces" << endl;
// Determine surface1 edge intersections. Allow surface to be moved.
// Number of iterations needed to resolve degenerates
label nIters1 = 0;
{
triSurfaceSearch querySurf2(surf2);
scalarField surf1PointTol
(
max(1e-3*edgeIntersections::minEdgeLength(surf1), SMALL)
);
// Determine raw intersections
edgeCuts1 = edgeIntersections
(
surf1,
querySurf2,
surf1PointTol
);
// Shuffle a bit to resolve degenerate edge-face hits
{
pointField points1(surf1.points());
nIters1 =
edgeCuts1.removeDegenerates
(
5, // max iterations
surf1,
querySurf2,
surf1PointTol,
points1 // work array
);
if (nIters1 != 0)
{
// Update geometric quantities
surf1.movePoints(points1);
hasMoved1 = true;
}
}
}
Info<< "Determining intersections of surf2 edges with surf1"
<< " faces" << endl;
label nIters2 = 0;
{
triSurfaceSearch querySurf1(surf1);
scalarField surf2PointTol
(
max(1e-3*edgeIntersections::minEdgeLength(surf2), SMALL)
);
// Determine raw intersections
edgeCuts2 = edgeIntersections
(
surf2,
querySurf1,
surf2PointTol
);
// Shuffle a bit to resolve degenerate edge-face hits
{
pointField points2(surf2.points());
nIters2 =
edgeCuts2.removeDegenerates
(
5, // max iterations
surf2,
querySurf1,
surf2PointTol,
points2 // work array
);
if (nIters2 != 0)
{
// Update geometric quantities
surf2.movePoints(points2);
hasMoved2 = true;
}
}
}
if (nIters1 == 0 && nIters2 == 0)
{
Info<< "** Resolved all intersections to be proper edge-face pierce"
<< endl;
break;
}
}
if (hasMoved1)
{
fileName newFile("surf1.obj");
Info<< "Surface 1 has been moved. Writing to " << newFile
<< endl;
surf1.write(newFile);
}
if (hasMoved2)
{
fileName newFile("surf2.obj");
Info<< "Surface 2 has been moved. Writing to " << newFile
<< endl;
surf2.write(newFile);
}
return hasMoved1 || hasMoved2;
}
label calcNormalDirection
(
const vector& normal,
const vector& otherNormal,
const vector& edgeDir,
const vector& faceCentre,
const vector& pointOnEdge
)
{
const vector cross = normalised(normal ^ edgeDir);
const vector fC0tofE0 = normalised(faceCentre - pointOnEdge);
label nDir = ((cross & fC0tofE0) > 0.0 ? 1 : -1);
nDir *= ((otherNormal & fC0tofE0) > 0.0 ? -1 : 1);
return nDir;
}
void calcEdgeCuts
(
triSurface& surf1,
triSurface& surf2,
const bool perturb,
edgeIntersections& edgeCuts1,
edgeIntersections& edgeCuts2
)
{
if (perturb)
{
intersectSurfaces
(
surf1,
edgeCuts1,
surf2,
edgeCuts2
);
}
else
{
triSurfaceSearch querySurf2(surf2);
Info<< "Determining intersections of surf1 edges with surf2 faces"
<< endl;
edgeCuts1 = edgeIntersections
(
surf1,
querySurf2,
max(1e-3*edgeIntersections::minEdgeLength(surf1), SMALL)
);
triSurfaceSearch querySurf1(surf1);
Info<< "Determining intersections of surf2 edges with surf1 faces"
<< endl;
edgeCuts2 = edgeIntersections
(
surf2,
querySurf1,
max(1e-3*edgeIntersections::minEdgeLength(surf2), SMALL)
);
}
}
// CGAL variants
#ifndef NO_CGAL
void visitPointRegion
(
const triSurface& s,
const label zoneI,
const label pointI,
const label startEdgeI,
const label startFaceI,
labelList& pFacesZone
)
{
const labelList& eFaces = s.edgeFaces()[startEdgeI];
if (eFaces.size() == 2)
{
label nextFaceI;
if (eFaces[0] == startFaceI)
{
nextFaceI = eFaces[1];
}
else if (eFaces[1] == startFaceI)
{
nextFaceI = eFaces[0];
}
else
{
FatalErrorInFunction
<< "problem" << exit(FatalError);
nextFaceI = -1;
}
label index = s.pointFaces()[pointI].find(nextFaceI);
if (pFacesZone[index] == -1)
{
// Mark face as been visited.
pFacesZone[index] = zoneI;
// Step to next edge on face which is still using pointI
const labelList& fEdges = s.faceEdges()[nextFaceI];
label nextEdgeI = -1;
forAll(fEdges, i)
{
label edgeI = fEdges[i];
const edge& e = s.edges()[edgeI];
if (edgeI != startEdgeI && (e[0] == pointI || e[1] == pointI))
{
nextEdgeI = edgeI;
break;
}
}
if (nextEdgeI == -1)
{
FatalErrorInFunction
<< "Problem: cannot find edge out of " << fEdges
<< "on face " << nextFaceI << " that uses point " << pointI
<< " and is not edge " << startEdgeI << abort(FatalError);
}
visitPointRegion
(
s,
zoneI,
pointI,
nextEdgeI,
nextFaceI,
pFacesZone
);
}
}
}
label dupNonManifoldPoints(triSurface& s, labelList& pointMap)
{
const labelListList& pf = s.pointFaces();
const labelListList& fe = s.faceEdges();
const edgeList& edges = s.edges();
DynamicField<point> newPoints(s.points());
// From dupSurf back to s.pointa
DynamicList<label> newPointMap(identity(newPoints.size()));
List<labelledTri> newFaces(s);
label nNonManifold = 0;
forAll(pf, pointI)
{
const labelList& pFaces = pf[pointI];
// Visited faces (as indices into pFaces)
labelList pFacesZone(pFaces.size(), -1);
label nZones = 0;
label index = 0;
for (; index < pFacesZone.size(); index++)
{
if (pFacesZone[index] == -1)
{
label zoneI = nZones++;
pFacesZone[index] = zoneI;
label faceI = pFaces[index];
const labelList& fEdges = fe[faceI];
// Find starting edge
forAll(fEdges, fEdgeI)
{
label edgeI = fEdges[fEdgeI];
const edge& e = edges[edgeI];
if (e[0] == pointI || e[1] == pointI)
{
visitPointRegion
(
s,
zoneI,
pointI,
edgeI,
faceI,
pFacesZone
);
}
}
}
}
// Subset
if (nZones > 1)
{
for (label zoneI = 1; zoneI < nZones; zoneI++)
{
label newPointI = newPoints.size();
newPointMap.append(s.meshPoints()[pointI]);
newPoints.append(s.points()[s.meshPoints()[pointI]]);
forAll(pFacesZone, index)
{
if (pFacesZone[index] == zoneI)
{
label faceI = pFaces[index];
const labelledTri& localF = s.localFaces()[faceI];
forAll(localF, fp)
{
if (localF[fp] == pointI)
{
newFaces[faceI][fp] = newPointI;
}
}
}
}
}
nNonManifold++;
}
}
Info<< "Duplicating " << nNonManifold << " points out of " << s.nPoints()
<< endl;
if (nNonManifold > 0)
{
triSurface dupSurf(newFaces, s.patches(), newPoints, true);
// Create map from dupSurf localPoints to s.localPoints
const Map<label> mpm = s.meshPointMap();
const labelList& dupMp = dupSurf.meshPoints();
labelList dupPointMap(dupMp.size());
forAll(dupMp, pointI)
{
label dupMeshPointI = dupMp[pointI];
label meshPointI = newPointMap[dupMeshPointI];
dupPointMap[pointI] = mpm[meshPointI];
}
forAll(dupPointMap, pointI)
{
const point& dupPt = dupSurf.points()[dupMp[pointI]];
label sLocalPointI = dupPointMap[pointI];
label sMeshPointI = s.meshPoints()[sLocalPointI];
const point& sPt = s.points()[sMeshPointI];
if (mag(dupPt-sPt) > SMALL)
{
FatalErrorInFunction
<< "dupPt:" << dupPt
<< " sPt:" << sPt
<< exit(FatalError);
}
}
//s.transfer(dupSurf);
s = dupSurf;
pointMap = labelUIndList(pointMap, dupPointMap)();
}
return nNonManifold;
}
// Find intersections of surf1 by edges of surf2. Return number of degenerate
// cuts (cuts through faces/edges/points)
labelPair edgeIntersectionsCGAL
(
const Tree& tree,
const triSurface& surf,
const pointField& points,
edgeIntersections& edgeCuts
)
{
const edgeList& edges = surf.edges();
const labelList& meshPoints = surf.meshPoints();
//Info<< "Intersecting CGAL surface ..." << endl;
List<List<pointIndexHit>> intersections(edges.size());
labelListList classifications(edges.size());
label nPoints = 0;
label nSegments = 0;
std::vector<Segment_intersection> segments;
forAll(edges, edgeI)
{
const edge& e = edges[edgeI];
const point& a = points[meshPoints[e[0]]];
const point& b = points[meshPoints[e[1]]];
K::Segment_3 segment_query
(
Point(a.x(), a.y(), a.z()),
Point(b.x(), b.y(), b.z())
);
segments.clear();
tree.all_intersections(segment_query, std::back_inserter(segments));
for (const Segment_intersection& intersect : segments)
{
// Get intersection object
if
(
const Point* ptPtr = boost::get<Point>(&(intersect->first))
)
{
point pt
(
CGAL::to_double(ptPtr->x()),
CGAL::to_double(ptPtr->y()),
CGAL::to_double(ptPtr->z())
);
#if defined (CGAL_VERSION_NR) && (CGAL_VERSION_NR < 1041400000)
Polyhedron::Face_handle f = (intersect->second);
#else
// 1.14 and later
Polyhedron::Face_handle f = (intersect->second).first;
#endif
intersections[edgeI].append
(
pointIndexHit
(
true,
pt,
f->index
)
);
// Intersection on edge interior
classifications[edgeI].append(-1);
++nPoints;
}
else if
(
const Segment* sPtr = boost::get<Segment>(&(intersect->first))
)
{
#if defined (CGAL_VERSION_NR) && (CGAL_VERSION_NR < 1041400000)
Polyhedron::Face_handle f = (intersect->second);
#else
// 1.14 and later
Polyhedron::Face_handle f = (intersect->second).first;
#endif
//std::cout
// << "intersection object is a segment:" << sPtr->source()
// << " " << sPtr->target() << std::endl;
//std::cout<< "triangle:" << f->index
// << " region:" << f->region << std::endl;
const point source
(
CGAL::to_double(sPtr->source().x()),
CGAL::to_double(sPtr->source().y()),
CGAL::to_double(sPtr->source().z())
);
const point target
(
CGAL::to_double(sPtr->target().x()),
CGAL::to_double(sPtr->target().y()),
CGAL::to_double(sPtr->target().z())
);
// Make up some intersection point
intersections[edgeI].append
(
pointIndexHit
(
true,
0.5*(source+target),
f->index
)
);
// Intersection aligned with face. Tbd: enums
classifications[edgeI].append(2);
++nSegments;
}
}
}
edgeCuts = edgeIntersections(intersections, classifications);
return labelPair(nPoints, nSegments);
}
// Intersect edges of surf1 until no more degenerate intersections. Return
// number of degenerates
labelPair edgeIntersectionsAndShuffleCGAL
(
Random& rndGen,
const triSurface& surf2,
const scalarField& surf1PointTol,
triSurface& surf1,
edgeIntersections& edgeCuts1
)
{
//Info<< "Constructing CGAL surface ..." << endl;
Polyhedron p;
PolyhedronReader(surf2, p);
//Info<< "Constructing CGAL tree ..." << endl;
const Tree tree(p.facets_begin(), p.facets_end(), p);
labelPair cuts(0, 0);
// Update surface1 points until no longer intersecting
pointField surf1Points(surf1.points());
for (label iter = 0; iter < 5; iter++)
{
// See which edges of 1 intersect 2
Info<< "Determining intersections of " << surf1.nEdges()
<< " edges with surface of " << label(tree.size()) << " triangles"
<< endl;
cuts = edgeIntersectionsCGAL
(
tree,
surf1,
surf1Points,
edgeCuts1
);
Info<< "Determined intersections:" << nl
<< " edges : " << surf1.nEdges() << nl
<< " non-degenerate cuts : " << cuts.first() << nl
<< " degenerate cuts : " << cuts.second() << nl
<< endl;
if (cuts.second() == 0)
{
break;
}
Info<< "Shuffling conflicting points" << endl;
const labelListList& edgeStat = edgeCuts1.classification();
const edgeList& edges = surf1.edges();
const labelList& mp = surf1.meshPoints();
const point p05(0.5, 0.5, 0.5);
forAll(edgeStat, edgeI)
{
const labelList& stat = edgeStat[edgeI];
forAll(stat, i)
{
if (stat[i] == 2)
{
const edge& e = edges[edgeI];
forAll(e, eI)
{
vector d = rndGen.sample01<vector>() - p05;
surf1Points[mp[e[eI]]] += surf1PointTol[e[eI]]*d;
}
}
}
}
}
surf1.movePoints(surf1Points);
return cuts;
}
// Return map from subSurf.edges() back to surf.edges()
labelList matchEdges
(
const triSurface& surf,
const triSurface& subSurf,
const labelList& pointMap
)
{
if (pointMap.size() != subSurf.nPoints())
{
FatalErrorInFunction
<< "problem" << exit(FatalError);
}
labelList edgeMap(subSurf.nEdges(), -1);
const edgeList& edges = surf.edges();
const labelListList& pointEdges = surf.pointEdges();
const edgeList& subEdges = subSurf.edges();
forAll(subEdges, subEdgeI)
{
const edge& subE = subEdges[subEdgeI];
// Match points on edge to those on surf
label start = pointMap[subE[0]];
label end = pointMap[subE[1]];
const labelList& pEdges = pointEdges[start];
forAll(pEdges, pEdgeI)
{
label edgeI = pEdges[pEdgeI];
const edge& e = edges[edgeI];
if (e.otherVertex(start) == end)
{
if (edgeMap[subEdgeI] == -1)
{
edgeMap[subEdgeI] = edgeI;
}
else if (edgeMap[subEdgeI] != edgeI)
{
FatalErrorInFunction
<< subE << " points:"
<< subE.line(subSurf.localPoints())
<< " matches to " << edgeI
<< " points:" << e.line(surf.localPoints())
<< " but also " << edgeMap[subEdgeI]
<< " points:"
<< edges[edgeMap[subEdgeI]].line(surf.localPoints())
<< exit(FatalError);
}
break;
}
}
if (edgeMap[subEdgeI] == -1)
{
FatalErrorInFunction
<< subE << " at:" << subSurf.localPoints()[subE[0]]
<< subSurf.localPoints()[subE[1]]
<< exit(FatalError);
}
}
return edgeMap;
}
void calcEdgeCutsCGAL
(
triSurface& surf1,
triSurface& surf2,
const bool perturb,
edgeIntersections& edgeCuts1,
edgeIntersections& edgeCuts2
)
{
if (!perturb)
{
// See which edges of 1 intersect 2
{
Info<< "Constructing CGAL surface ..." << endl;
Polyhedron p;
PolyhedronReader(surf2, p);
Info<< "Constructing CGAL tree ..." << endl;
const Tree tree(p.facets_begin(), p.facets_end(), p);
edgeIntersectionsCGAL
(
tree,
surf1,
surf1.points(),
edgeCuts1
);
}
// See which edges of 2 intersect 1
{
Info<< "Constructing CGAL surface ..." << endl;
Polyhedron p;
PolyhedronReader(surf1, p);
Info<< "Constructing CGAL tree ..." << endl;
const Tree tree(p.facets_begin(), p.facets_end(), p);
edgeIntersectionsCGAL
(
tree,
surf2,
surf2.points(),
edgeCuts2
);
}
}
else
{
const scalarField surf1PointTol
(
max(1e-8*edgeIntersections::minEdgeLength(surf1), SMALL)
);
const scalarField surf2PointTol
(
max(1e-8*edgeIntersections::minEdgeLength(surf2), SMALL)
);
Random rndGen(0);
labelPair cuts1;
labelPair cuts2;
for (label iter = 0; iter < 10; iter++)
{
// Find intersections of surf1 edges with surf2 triangles
cuts1 = edgeIntersectionsAndShuffleCGAL
(
rndGen,
surf2,
surf1PointTol,
surf1,
edgeCuts1
);
// Find intersections of surf2 edges with surf1 triangles
cuts2 = edgeIntersectionsAndShuffleCGAL
(
rndGen,
surf1,
surf2PointTol,
surf2,
edgeCuts2
);
if (cuts1.second() + cuts2.second() == 0)
{
break;
}
}
}
}
void calcEdgeCutsBitsCGAL
(
triSurface& surf1,
triSurface& surf2,
const bool perturb,
edgeIntersections& edgeCuts1,
edgeIntersections& edgeCuts2
)
{
label nZones1 = 1;
labelList zone1;
{
labelHashSet orientationEdge(surf1.size()/1000);
PatchTools::checkOrientation(surf1, false, &orientationEdge);
nZones1 = PatchTools::markZones(surf1, orientationEdge, zone1);
Info<< "Surface triangles " << surf1.size()
<< " number of zones (orientation or non-manifold):"
<< nZones1 << endl;
}
label nZones2 = 1;
labelList zone2;
{
labelHashSet orientationEdge(surf2.size()/1000);
PatchTools::checkOrientation(surf2, false, &orientationEdge);
nZones2 = PatchTools::markZones(surf2, orientationEdge, zone2);
Info<< "Surface triangles " << surf2.size()
<< " number of zones (orientation or non-manifold):"
<< nZones2 << endl;
}
if (nZones1 == 1 && nZones2 == 1)
{
calcEdgeCutsCGAL
(
surf1,
surf2,
perturb,
edgeCuts1,
edgeCuts2
);
}
else
{
edgeCuts1 = edgeIntersections
(
List<List<pointIndexHit>>(surf1.nEdges()),
labelListList(surf1.nEdges())
);
edgeCuts2 = edgeIntersections
(
List<List<pointIndexHit>>(surf2.nEdges()),
labelListList(surf2.nEdges())
);
for (label zone1I = 0; zone1I < nZones1; zone1I++)
{
// Generate sub surface for zone1I
boolList includeMap1(surf1.size(), false);
forAll(zone1, faceI)
{
if (zone1[faceI] == zone1I)
{
includeMap1[faceI] = true;
}
}
// Subset. Map from local points on subset to local points on
// original
labelList pointMap1;
labelList faceMap1;
triSurface subSurf1
(
surf1.subsetMesh
(
includeMap1,
pointMap1,
faceMap1
)
);
// Split non-manifold points; update pointMap
dupNonManifoldPoints(subSurf1, pointMap1);
const boundBox subBb1
(
subSurf1.points(),
subSurf1.meshPoints(),
false
);
const labelList edgeMap1
(
matchEdges
(
surf1,
subSurf1,
pointMap1
)
);
for (label zone2I = 0; zone2I < nZones2; zone2I++)
{
// Generate sub surface for zone2I
boolList includeMap2(surf2.size(), false);
forAll(zone2, faceI)
{
if (zone2[faceI] == zone2I)
{
includeMap2[faceI] = true;
}
}
labelList pointMap2;
labelList faceMap2;
triSurface subSurf2
(
surf2.subsetMesh
(
includeMap2,
pointMap2,
faceMap2
)
);
const boundBox subBb2
(
subSurf2.points(),
subSurf2.meshPoints(),
false
);
// Short-circuit expensive calculations
if (!subBb2.overlaps(subBb1))
{
continue;
}
// Split non-manifold points; update pointMap
dupNonManifoldPoints(subSurf2, pointMap2);
const labelList edgeMap2
(
matchEdges
(
surf2,
subSurf2,
pointMap2
)
);
// Do cuts
edgeIntersections subEdgeCuts1;
edgeIntersections subEdgeCuts2;
calcEdgeCutsCGAL
(
subSurf1,
subSurf2,
perturb,
subEdgeCuts1,
subEdgeCuts2
);
// Move original surface
{
pointField points2(surf2.points());
forAll(pointMap2, i)
{
label subMeshPointI = subSurf2.meshPoints()[i];
label meshPointI = surf2.meshPoints()[pointMap2[i]];
points2[meshPointI] = subSurf2.points()[subMeshPointI];
}
surf2.movePoints(points2);
}
// Insert into main structure
edgeCuts1.merge(subEdgeCuts1, edgeMap1, faceMap2);
edgeCuts2.merge(subEdgeCuts2, edgeMap2, faceMap1);
}
// Move original surface
{
pointField points1(surf1.points());
forAll(pointMap1, i)
{
label subMeshPointI = subSurf1.meshPoints()[i];
label meshPointI = surf1.meshPoints()[pointMap1[i]];
points1[meshPointI] = subSurf1.points()[subMeshPointI];
}
surf1.movePoints(points1);
}
}
}
}
#endif // NO_CGAL
//void calcFeaturePoints(const pointField& points, const edgeList& edges)
//{
// edgeMesh eMesh(points, edges);
//
// const labelListList& pointEdges = eMesh.pointEdges();
//
//
// // Get total number of feature points
// label nFeaturePoints = 0;
// forAll(pointEdges, pI)
// {
// const labelList& pEdges = pointEdges[pI];
//
// if (pEdges.size() == 1)
// {
// nFeaturePoints++;
// }
// }
//
//
// // Calculate addressing from feature point to cut point and cut edge
// labelList featurePointToCutPoint(nFeaturePoints);
// labelList featurePointToCutEdge(nFeaturePoints);
//
// label nFeatPts = 0;
// forAll(pointEdges, pI)
// {
// const labelList& pEdges = pointEdges[pI];
//
// if (pEdges.size() == 1)
// {
// featurePointToCutPoint[nFeatPts] = pI;
// featurePointToCutEdge[nFeatPts] = pEdges[0];
// nFeatPts++;
// }
// }
//
//
//
// label concaveStart = 0;
// label mixedStart = 0;
// label nonFeatureStart = nFeaturePoints;
//
//
// labelListList featurePointNormals(nFeaturePoints);
// labelListList featurePointEdges(nFeaturePoints);
// labelList regionEdges;
//}
autoPtr<extendedFeatureEdgeMesh> createEdgeMesh
(
const IOobject& io,
const booleanSurface::booleanOpType action,
const bool surf1Baffle,
const bool surf2Baffle,
const bool invertedSpace,
const triSurface& surf1,
const edgeIntersections& edgeCuts1,
const triSurface& surf2,
const edgeIntersections& edgeCuts2
)
{
// Determine intersection edges from the edge cuts
surfaceIntersection inter
(
surf1,
edgeCuts1,
surf2,
edgeCuts2
);
label nFeatEds = inter.cutEdges().size();
DynamicList<vector> normals(2*nFeatEds);
vectorField edgeDirections(nFeatEds, Zero);
DynamicList<extendedFeatureEdgeMesh::sideVolumeType> normalVolumeTypes
(
2*nFeatEds
);
List<DynamicList<label>> edgeNormals(nFeatEds);
List<DynamicList<label>> normalDirections(nFeatEds);
const triSurface& s1 = surf1;
const triSurface& s2 = surf2;
forAllConstIters(inter.facePairToEdgeId(), iter)
{
const labelPair& facePair = iter.key();
const label cutEdgeI = iter.val();
const edge& fE = inter.cutEdges()[cutEdgeI];
const vector& norm1 = s1.faceNormals()[facePair.first()];
const vector& norm2 = s2.faceNormals()[facePair.second()];
DynamicList<label>& eNormals = edgeNormals[cutEdgeI];
DynamicList<label>& nDirections = normalDirections[cutEdgeI];
edgeDirections[cutEdgeI] = fE.vec(inter.cutPoints());
normals.append(norm1);
eNormals.append(normals.size() - 1);
if (surf1Baffle)
{
normalVolumeTypes.append(extendedFeatureEdgeMesh::BOTH);
nDirections.append(1);
}
else
{
normalVolumeTypes.append(extendedFeatureEdgeMesh::INSIDE);
nDirections.append
(
calcNormalDirection
(
norm1,
norm2,
edgeDirections[cutEdgeI],
s1[facePair.first()].centre(s1.points()),
inter.cutPoints()[fE.start()]
)
);
}
normals.append(norm2);
eNormals.append(normals.size() - 1);
if (surf2Baffle)
{
normalVolumeTypes.append(extendedFeatureEdgeMesh::BOTH);
nDirections.append(1);
}
else
{
normalVolumeTypes.append(extendedFeatureEdgeMesh::INSIDE);
nDirections.append
(
calcNormalDirection
(
norm2,
norm1,
edgeDirections[cutEdgeI],
s2[facePair.second()].centre(s2.points()),
inter.cutPoints()[fE.start()]
)
);
}
if (surf1Baffle)
{
normals.append(norm2);
if (surf2Baffle)
{
normalVolumeTypes.append(extendedFeatureEdgeMesh::BOTH);
nDirections.append(1);
}
else
{
normalVolumeTypes.append(extendedFeatureEdgeMesh::INSIDE);
nDirections.append
(
calcNormalDirection
(
norm2,
norm1,
edgeDirections[cutEdgeI],
s2[facePair.second()].centre(s2.points()),
inter.cutPoints()[fE.start()]
)
);
}
eNormals.append(normals.size() - 1);
}
if (surf2Baffle)
{
normals.append(norm1);
if (surf1Baffle)
{
normalVolumeTypes.append(extendedFeatureEdgeMesh::BOTH);
nDirections.append(1);
}
else
{
normalVolumeTypes.append(extendedFeatureEdgeMesh::INSIDE);
nDirections.append
(
calcNormalDirection
(
norm1,
norm2,
edgeDirections[cutEdgeI],
s1[facePair.first()].centre(s1.points()),
inter.cutPoints()[fE.start()]
)
);
}
eNormals.append(normals.size() - 1);
}
}
label internalStart = -1;
label nIntOrExt = 0;
label nFlat = 0;
label nOpen = 0;
label nMultiple = 0;
forAll(edgeNormals, eI)
{
label nEdNorms = edgeNormals[eI].size();
if (nEdNorms == 1)
{
nOpen++;
}
else if (nEdNorms == 2)
{
const vector& n0(normals[edgeNormals[eI][0]]);
const vector& n1(normals[edgeNormals[eI][1]]);
if ((n0 & n1) > extendedFeatureEdgeMesh::cosNormalAngleTol_)
{
nFlat++;
}
else
{
nIntOrExt++;
}
}
else if (nEdNorms > 2)
{
nMultiple++;
}
}
if (action == booleanSurface::UNION)
{
if (!invertedSpace)
{
// All edges are internal
internalStart = 0;
}
else
{
// All edges are external
internalStart = nIntOrExt;
}
}
else if (action == booleanSurface::INTERSECTION)
{
if (!invertedSpace)
{
// All edges are external
internalStart = nIntOrExt;
}
else
{
// All edges are internal
internalStart = 0;
}
}
else if (action == booleanSurface::DIFFERENCE)
{
// All edges are external
internalStart = nIntOrExt;
}
else
{
FatalErrorInFunction
<< "Unsupported booleanSurface:booleanOpType and space "
<< action << " " << invertedSpace
<< abort(FatalError);
}
// There are no feature points supported by surfaceIntersection
// Flat, open or multiple edges are assumed to be impossible
// Region edges are not explicitly supported by surfaceIntersection
vectorField normalsTmp(normals);
List<extendedFeatureEdgeMesh::sideVolumeType> normalVolumeTypesTmp
(
normalVolumeTypes
);
labelListList edgeNormalsTmp(edgeNormals.size());
forAll(edgeNormalsTmp, i)
{
edgeNormalsTmp[i] = edgeNormals[i];
}
labelListList normalDirectionsTmp(normalDirections.size());
forAll(normalDirectionsTmp, i)
{
normalDirectionsTmp[i] = normalDirections[i];
}
//calcFeaturePoints(inter.cutPoints(), inter.cutEdges());
return autoPtr<extendedFeatureEdgeMesh>::New
(
io,
inter.cutPoints(),
inter.cutEdges(),
0, // concaveStart,
0, // mixedStart,
0, // nonFeatureStart,
internalStart, // internalStart,
nIntOrExt, // flatStart,
nIntOrExt + nFlat, // openStart,
nIntOrExt + nFlat + nOpen, // multipleStart,
normalsTmp,
normalVolumeTypesTmp,
edgeDirections,
normalDirectionsTmp,
edgeNormalsTmp,
labelListList(), // featurePointNormals,
labelListList(), // featurePointEdges,
labelList() // regionEdges
);
}
int main(int argc, char *argv[])
{
argList::addNote
(
"Generates the extendedFeatureEdgeMesh for the interface created by"
" a boolean operation on two surfaces."
);
argList::noParallel();
argList::addArgument
(
"action",
"One of (intersection | union | difference)"
);
argList::addArgument("surface1", "The input surface file 1");
argList::addArgument("surface2", "The input surface file 2");
argList::addOption
(
"scale",
"factor",
"Geometry scaling factor (both surfaces)"
);
argList::addBoolOption
(
"surf1Baffle",
"Mark surface 1 as a baffle"
);
argList::addBoolOption
(
"surf2Baffle",
"Mark surface 2 as a baffle"
);
argList::addBoolOption
(
"perturb",
"Perturb surface points to escape degenerate intersections"
);
argList::addBoolOption
(
"invertedSpace",
"Do the surfaces have inverted space orientation, "
"i.e. a point at infinity is considered inside. "
"This is only sensible for union and intersection."
);
argList::addOption
(
"trim",
"((surface1 volumeType) .. (surfaceN volumeType))",
"Trim resulting intersection with additional surfaces;"
" volumeType is 'inside' (keep (parts of) edges that are inside)"
", 'outside' (keep (parts of) edges that are outside) or"
" 'mixed' (keep all)"
);
#include "setRootCase.H"
#include "createTime.H"
const word action(args[1]);
const Enum<booleanSurface::booleanOpType> validActions
{
{ booleanSurface::INTERSECTION, "intersection" },
{ booleanSurface::UNION, "union" },
{ booleanSurface::DIFFERENCE, "difference" }
};
if (!validActions.found(action))
{
FatalErrorInFunction
<< "Unsupported action " << action << endl
<< "Supported actions:" << validActions << nl
<< abort(FatalError);
}
List<Pair<word>> surfaceAndSide;
if (args.readIfPresent("trim", surfaceAndSide))
{
Info<< "Trimming edges with " << surfaceAndSide << endl;
}
// Scale factor for both surfaces:
const scalar scaleFactor = args.get<scalar>("scale", -1);
const word surf1Name(args[2]);
Info<< "Reading surface " << surf1Name << endl;
triSurfaceMesh surf1
(
IOobject
(
surf1Name,
runTime.constant(),
triSurfaceMesh::meshSubDir,
runTime
)
);
if (scaleFactor > 0)
{
Info<< "Scaling : " << scaleFactor << nl;
surf1.scalePoints(scaleFactor);
}
Info<< surf1Name << " statistics:" << endl;
surf1.writeStats(Info);
Info<< endl;
const word surf2Name(args[3]);
Info<< "Reading surface " << surf2Name << endl;
triSurfaceMesh surf2
(
IOobject
(
surf2Name,
runTime.constant(),
triSurfaceMesh::meshSubDir,
runTime
)
);
if (scaleFactor > 0)
{
Info<< "Scaling : " << scaleFactor << nl;
surf2.scalePoints(scaleFactor);
}
Info<< surf2Name << " statistics:" << endl;
surf2.writeStats(Info);
Info<< endl;
const bool surf1Baffle = args.found("surf1Baffle");
const bool surf2Baffle = args.found("surf2Baffle");
edgeIntersections edgeCuts1;
edgeIntersections edgeCuts2;
const bool invertedSpace = args.found("invertedSpace");
if (invertedSpace && validActions[action] == booleanSurface::DIFFERENCE)
{
FatalErrorInFunction
<< "Inverted space only makes sense for union or intersection."
<< exit(FatalError);
}
#ifdef NO_CGAL
// Calculate the points where the edges are cut by the other surface
calcEdgeCuts
(
surf1,
surf2,
args.found("perturb"),
edgeCuts1,
edgeCuts2
);
#else
//calcEdgeCutsCGAL
calcEdgeCutsBitsCGAL
(
surf1,
surf2,
args.found("perturb"),
edgeCuts1,
edgeCuts2
);
#endif // NO_CGAL
const fileName sFeatFileName
(
fileName(surf1Name).nameLessExt()
+ "_"
+ fileName(surf2Name).nameLessExt()
+ "_"
+ action
);
autoPtr<extendedFeatureEdgeMesh> feMeshPtr
(
createEdgeMesh
(
IOobject
(
sFeatFileName + ".extendedFeatureEdgeMesh",
runTime.constant(),
"extendedFeatureEdgeMesh",
runTime,
IOobject::NO_READ,
IOobject::NO_WRITE
),
booleanSurface::booleanOpTypeNames[action],
surf1Baffle,
surf2Baffle,
invertedSpace,
surf1,
edgeCuts1,
surf2,
edgeCuts2
)
);
// Trim intersections
forAll(surfaceAndSide, trimI)
{
const word& trimName = surfaceAndSide[trimI].first();
const volumeType trimType
(
volumeType::names[surfaceAndSide[trimI].second()]
);
Info<< "Reading trim surface " << trimName << endl;
const triSurfaceMesh trimSurf
(
IOobject
(
trimName,
runTime.constant(),
triSurfaceMesh::meshSubDir,
runTime,
IOobject::MUST_READ,
IOobject::NO_WRITE
)
);
Info<< trimName << " statistics:" << endl;
trimSurf.writeStats(Info);
Info<< endl;
labelList pointMap;
labelList edgeMap;
feMeshPtr().trim
(
trimSurf,
trimType,
pointMap,
edgeMap
);
}
const extendedFeatureEdgeMesh& feMesh = feMeshPtr();
feMesh.writeStats(Info);
feMesh.write();
feMesh.writeObj(feMesh.path()/sFeatFileName);
{
// Write a featureEdgeMesh for backwards compatibility
featureEdgeMesh bfeMesh
(
IOobject
(
sFeatFileName + ".eMesh", // name
runTime.constant(), // instance
triSurfaceMesh::meshSubDir,
runTime, // registry
IOobject::NO_READ,
IOobject::NO_WRITE,
false
),
feMesh.points(),
feMesh.edges()
);
Info<< nl << "Writing featureEdgeMesh to "
<< bfeMesh.objectPath() << endl;
bfeMesh.regIOobject::write();
}
Info << "End\n" << endl;
return 0;
}
// ************************************************************************* //