xmsmesh  1.0
Meshing_Tutorial

Meshing Tutorial

Introduction

The purpose of this tutorial is to provide explanation on how to use the classes defined in xmsmesh to generate meshes from input polygons. The examples provided in this tutorial refer to test cases that are in the xmsmesh/tutorial/TutMeshing.cpp source file.

Example - Simple Polygon

This is the "hello world" example for using the meshing library.

This first example shows how to mesh a single polygon. The testing code for this example is TutMeshingTests::test_Example_SimplePolygon. A picture of the example is shown below. Notice that the polygon is a simple square from (0,0) to (100,100). Also notice that the point spacing along the boundary is a constant value of 10.

tutMesh_SimplePolygon_Input.png
Simple Polygon with boundary spacing = 10.0

The basic steps to generate a mesh from a polygon are:

  1. Define the polygon as a vector of points using the xms::MePolyInput class.
  2. Add the instance of the xms::MePolyInput class to the m_polys vector of the xms::MeMultiPolyMesherInput class.
  3. Pass the xms::MeMultiPolyMesherInput class to the xms::MeMultiPolyTo2dm->Generate2dm method. This call will create an ascii file (2dm).
{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {
{0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 50}, {0, 60}, {0, 70}, {0, 80},
{0, 90}, {0, 100}, {10, 100}, {20, 100}, {30, 100}, {40, 100}, {50, 100}, {60, 100},
{70, 100}, {80, 100}, {90, 100}, {100, 100}, {100, 90}, {100, 80}, {100, 70}, {100, 60},
{100, 50}, {100, 40}, {100, 30}, {100, 20}, {100, 10}, {100, 0}, {90, 0}, {80, 0},
{70, 0}, {60, 0}, {50, 0}, {40, 0}, {30, 0}, {20, 0}, {10, 0}, {0, 0}};
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// generate the mesh and check the base line
const std::string baseFile = "Example_SimplePolygon";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_SimplePolygon

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this mesh can be found at test_files/Tutorial_Meshing/Example_SimplePolygon_base.2dm.

tutMesh_SimplePolygon_Output.png
2d mesh generated from simple polygon with boundary spacing = 10.0

Example - Complex Polygon

This is another example of how to mesh a single polygon. The testing code for this example is TutMeshingTests::test_Example_ComplexPolygon. A picture of the example is shown below. Notice that the polygon is more complex than the first case. Similar to example 0, the point spacing along the boundary is a constant value of 10.

tutMesh_ComplexPolygon_Input.png
Complex Polygon with boundary spacing = 10.0

The same basic steps used with the simple polygon are followed for this example.

{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {
{0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 50}, {0, 60}, {0, 70}, {0, 80},
{0, 90}, {0, 100}, {0, 110}, {0, 120}, {0, 130}, {0, 140}, {0, 150}, {0, 160},
{0, 170}, {0, 180}, {0, 190}, {0, 200}, {10, 200}, {20, 200}, {30, 200}, {40, 200},
{50, 200}, {60, 200}, {70, 200}, {80, 200}, {90, 200}, {100, 200}, {110, 200}, {120, 200},
{130, 200}, {140, 200}, {150, 200}, {160, 200}, {170, 200}, {180, 200}, {190, 200}, {200, 200},
{200, 190}, {200, 180}, {200, 170}, {200, 160}, {200, 150}, {200, 140}, {200, 130}, {200, 120},
{200, 110}, {200, 100}, {200, 90}, {200, 80}, {200, 70}, {200, 60}, {200, 50}, {200, 40},
{200, 30}, {200, 20}, {200, 10}, {200, 0}, {190, 0}, {180, 0}, {170, 0}, {160, 0},
{150, 0}, {140, 0}, {130, 0}, {120, 0}, {110, 0}, {110, 10}, {110, 20}, {110, 30},
{110, 40}, {120, 40}, {130, 40}, {140, 40}, {150, 40}, {150, 50}, {150, 60}, {150, 70},
{150, 80}, {150, 90}, {150, 100}, {150, 110}, {150, 120}, {150, 130}, {150, 140}, {150, 150},
{140, 150}, {130, 150}, {120, 150}, {110, 150}, {100, 150}, {90, 150}, {80, 150}, {70, 150},
{60, 150}, {50, 150}, {50, 140}, {50, 130}, {50, 120}, {50, 110}, {50, 100}, {50, 90},
{50, 80}, {50, 70}, {50, 60}, {50, 50}, {50, 40}, {60, 40}, {70, 40}, {80, 40},
{90, 40}, {90, 30}, {90, 20}, {90, 10}, {90, 0}, {80, 0}, {70, 0}, {60, 0},
{50, 0}, {40, 0}, {30, 0}, {20, 0}, {10, 0}, {0, 0}};
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// generate the mesh and check the base line
const std::string baseFile = "Example_ComplexPolygon";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_ComplexPolygon

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_ComplexPolygon_base.2dm

tutMesh_ComplexPolygon_Output.png
2d mesh generated from complex polygon with boundary spacing = 10.0

Example - Simple Polygon with a hole

This is another example of how to mesh a single polygon, but this polygon contains a hole. The testing code for this example is TutMeshingTests::test_Example_SimplePolygonWithHole. A picture of the example is shown below. This example still uses a constant point spacing of 10 along the boundary.

tutMesh_SimplePolygonWithHole_Input.png
Simple Polygon with a hole and boundary spacing = 10.0
{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {
{0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 50}, {0, 60}, {0, 70}, {0, 80},
{0, 90}, {0, 100}, {10, 100}, {20, 100}, {30, 100}, {40, 100}, {50, 100}, {60, 100},
{70, 100}, {80, 100}, {90, 100}, {100, 100}, {100, 90}, {100, 80}, {100, 70}, {100, 60},
{100, 50}, {100, 40}, {100, 30}, {100, 20}, {100, 10}, {100, 0}, {90, 0}, {80, 0},
{70, 0}, {60, 0}, {50, 0}, {40, 0}, {30, 0}, {20, 0}, {10, 0}, {0, 0}};
// Inner polygons are in counter clockwise order. Do not repeat the first
// point.
inputPoly.m_insidePolys.push_back(xms::VecPt3d());
inputPoly.m_insidePolys[0] = {{40, 40}, {50, 40}, {60, 40}, {60, 50},
{60, 60}, {50, 60}, {40, 60}, {40, 50}};
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// generate the mesh and check the base line
const std::string baseFile = "Example_SimplePolygonWithHole";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_SimplePolygonWithHole

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_SimplePolygonWithHole_base.2dm

tutMesh_SimplePolygonWithHole_Output.png
2d mesh generated from simple polygon with a hole and boundary spacing = 10.0

Example - Breaklines

The next example has a polygon as well as a breakline. A breakline is a mulitsegment line within a polygon. The breakline may begin at one of the polygon points or it may be completely contained within the polygon. The edges of elements in the resulting mesh must conform to the segments of the breakline. The testing code for this example is TutMeshingTests::test_Example_Breakline. A picture of the example is shown below.

tutMesh_Breakline_Input.png
Simple Polygon with a breakline and boundary spacing = 10.0

Breaklines can be specified as an internal polygon with a width of 0.0. The points that make up the line are pushed into a vector from the beginning point in order to the endpoint and then back in order to the point just before the beginning point (see the definition of the inner polygon in the following example).

{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {
{0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 50}, {0, 60}, {0, 70}, {0, 80},
{0, 90}, {0, 100}, {10, 100}, {20, 100}, {30, 100}, {40, 100}, {50, 100}, {60, 100},
{70, 100}, {80, 100}, {90, 100}, {100, 100}, {100, 90}, {100, 80}, {100, 70}, {100, 60},
{100, 50}, {100, 40}, {100, 30}, {100, 20}, {100, 10}, {100, 0}, {90, 0}, {80, 0},
{70, 0}, {60, 0}, {50, 0}, {40, 0}, {30, 0}, {20, 0}, {10, 0}, {0, 0}};
// Inner polygons are in counter clockwise order. Do not repeat the first
// point.
inputPoly.m_insidePolys.push_back(xms::VecPt3d());
inputPoly.m_insidePolys[0] = {{50, 0}, {50, 10}, {50, 20}, {50, 10}};
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// generate the mesh and check the base line
const std::string baseFile = "Example_Breakline";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_Breakline

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_Breakline_base.2dm

tutMesh_Breakline_Output.png
2d mesh generated from simple polygon with a hole and boundary spacing = 10.0

Example - Refine Points

A refine point is a location in the mesh where the user can specify a desired element edge size. The generated mesh will have elements with the specified size surrounding the refine point. Refine points can be located at a mesh node/point (corner of a cell/element). Refine points can also be located at the center of a cell/element. A desired element edge size can also be specified with each refine point. If the size is not specified then the refine point becomes a "hard point" that is inserted as a node into the mesh. The following example includes all 3 types of refine points. The testing code for this example is TutMeshingTests::test_Example_RefinePoints. A picture of the example is shown below.

tutMesh_RefinePoints_Input.png
Simple Polygon with 3 refine points and boundary spacing = 10.0
{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {
{0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 50}, {0, 60}, {0, 70}, {0, 80},
{0, 90}, {0, 100}, {10, 100}, {20, 100}, {30, 100}, {40, 100}, {50, 100}, {60, 100},
{70, 100}, {80, 100}, {90, 100}, {100, 100}, {100, 90}, {100, 80}, {100, 70}, {100, 60},
{100, 50}, {100, 40}, {100, 30}, {100, 20}, {100, 10}, {100, 0}, {90, 0}, {80, 0},
{70, 0}, {60, 0}, {50, 0}, {40, 0}, {30, 0}, {20, 0}, {10, 0}, {0, 0}};
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// Refine points are specified independent of polygons. The mesher will
// determine the polygon that contains the point.
input.m_refPts.assign(3, xms::MeRefinePoint(xms::Pt3d(), -1, true));
// specify a refine point where the point is at the cell center with
// a desired size of 2
input.m_refPts[0].m_pt = xms::Pt3d(20, 20, 0);
input.m_refPts[0].m_createMeshPoint = false;
input.m_refPts[0].m_size = 2.0;
// specify a refine point where the point is at a mesh node with a
// desired size of 7
input.m_refPts[1].m_pt = xms::Pt3d(20, 80, 0);
input.m_refPts[1].m_createMeshPoint = true;
input.m_refPts[1].m_size = 5.0;
// specify a "hard point"
input.m_refPts[2].m_pt = xms::Pt3d(80, 20, 0);
input.m_refPts[2].m_size = -1;
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// generate the mesh and check the base line
const std::string baseFile = "Example_RefinePoints";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_RefinePoints

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_RefinePoints_base.2dm

tutMesh_RefinePoints_Output.png
2d mesh generated from simple polygon with 3 refine points and boundary spacing = 10.0

Example - Multiple Polygons with variable spacing, holes, breaklines, and refine points

The next example has multiple polygons with variable spacing along the boundary, holes in the polygons, breaklines and refine points. The testing code for this example is TutMeshingTests::test_Example_MultiplePolygons. A picture of the example is shown below.

tutMesh_MultiPolygon_Input.png
Multiple polygons with variable spacing, holes, breaklines, and refine points
{
const std::string path(std::string(XMS_TEST_PATH) + "Tutorial_Meshing/");
const std::string fname(path + "Example_MultiPolys.txt");
xms::VecPt3d3d inside;
xms::VecPt3d2d outside;
// Read the polygons from a text file to avoid typing out all of the
// coordinates
xms::tutReadPolygons(fname, outside, inside);
// put the polygons into the meshing input class
for (size_t i = 0; i < outside.size(); ++i)
{
input.m_polys.push_back(xms::MePolyInput(outside[i], inside[i]));
}
// add refine points to the meshing input
xms::Pt3d p0(80, 80, 0), p1(125, 125, 0);
input.m_refPts.push_back(xms::MeRefinePoint(p0, -1.0, true));
input.m_refPts.push_back(xms::MeRefinePoint(p1, 1.0, true));
// generate the mesh and check the base line
const std::string baseFile = "Example_MultiPolys";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_MultiplePolygons

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_MultiPolygon_base.2dm

tutMesh_MultiPolygon_Output.png
2d mesh generated from multiple polygons

Example - Scalar Paving

This example illustrates how to influence the size of elements in the generated 2d mesh by specifying a size function. This process is referred to as scalar paving. The size function is specified using xms::InterpBase. The InterpBase class performs spatial interpolation from points and triangles. This example uses a simple polygon with a set of 5 points and 4 triangles to define a linear size function. The testing code for this example is TutMeshingTests::test_Example_ScalarPaving. A picture of the example is shown below.

tutMesh_ScalarPaving_Input.png
Simple polygon with linear size function
{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {
{0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 50}, {0, 60}, {0, 70}, {0, 80},
{0, 90}, {0, 100}, {10, 100}, {20, 100}, {30, 100}, {40, 100}, {50, 100}, {60, 100},
{70, 100}, {80, 100}, {90, 100}, {100, 100}, {100, 90}, {100, 80}, {100, 70}, {100, 60},
{100, 50}, {100, 40}, {100, 30}, {100, 20}, {100, 10}, {100, 0}, {90, 0}, {80, 0},
{70, 0}, {60, 0}, {50, 0}, {40, 0}, {30, 0}, {20, 0}, {10, 0}, {0, 0}};
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// create a size function interpolator
// These are the interpolator point locations. The size is specified by the
// "z" component of the points.
BSHP<xms::VecPt3d> sPts(new xms::VecPt3d());
*sPts = {{-10, -10, 10}, {-10, 110, 10}, {110, 110, 10}, {110, -10, 10}, {60, 70, 1}};
// These are the interpolator triangles.
// Triangles are specified as point indexes in ccw order. You can see the
// 4 triangles in the vector below.
BSHP<xms::VecInt> sTris(new xms::VecInt());
*sTris = {0, 4, 1, 1, 4, 2, 2, 4, 3, 3, 4, 0};
// create a linear interpolator for the size function
BSHP<xms::InterpBase> linear(xms::InterpLinear::New());
// sets the points and the triangles for the interpolator
linear->SetPtsTris(sPts, sTris);
// now set the size function on the mesh generator input class
input.m_polys[0].m_sizeFunction = linear;
// generate the mesh and check the base line
const std::string baseFile = "Example_ScalarPaving";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_ScalarPaving

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_ScalarPaving_base.2dm

tutMesh_ScalarPaving_Output.png
2d mesh generated from simple polygon with size function

Example - Patch Mesh Generation

An adaptive coons patch methodology is implemented in xmsmesh for generating triangular and quad cells. In general, a patch can be generated for a polygon with 4 sides. You can specify the four sides by indicating the indices of the points that make up the polygon corners in the xms::MePolyInput class (m_polyCorners). Below is a picture of the input for this example. Notice that the number of segments is different on each side of the polygon. The testing code for this example is TutMeshingTests::test_Example_Patch.

tutMesh_Patch_Input.png
Input polygon for patch mesh generation

The following code shows how to setup the polygon corners. Note that the first point listed in the polygons is assumed to be a corner and not included in the list of corners. Thus, m_polyCorners should always be either size 0 or 3.

{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {{0, 0}, {0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 60},
{0, 70}, {0, 80}, {0, 90}, {0, 100}, {20, 100}, {40, 100},
{60, 100}, {100, 100}, {100, 90}, {100, 80}, {100, 70}, {100, 60},
{100, 50}, {100, 40}, {100, 30}, {100, 20}, {100, 10}, {100, 0},
{85, 0}, {70, 0}, {55, 0}, {40, 0}, {25, 0}, {10, 0}};
// Specify the polygon corners. It is assumed that the first point above is
// one of the corners so we specify the other 3 corners.
inputPoly.m_polyCorners = {9, 13, 23};
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// generate the mesh and check the base line
const std::string baseFile = "Example_Patch";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_Patch

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_Patch_base.2dm

tutMesh_Patch_Output.png
2d mesh generated using the patch algorithm

Checking Meshing Input for Errors

The input to the meshing algorithm is done using the xms::MeMultiPolyMesherInput class and the input must be specified in a certain way. For example, vectors defining outer polygons must not be empty, the first point should not be repeated as the last point, and the number of patch corners should be 0 or 3. The function MeMultiPolyMesherImpl::ValidateInput always checks the input for these simple errors before the meshing is performed. If errors are found, the mesher asserts, writes the errors to a log file, and aborts.

Additionally, no polygon should intersect with any other polygon. A check for polygon intersections can be performed if the xms::MeMultiPolyMesherInput::m_checkTopology variable is set to true. Checking for intersections can take a long time if the input includes many polygons, so this variable is false by default. Again, if errors are found, the mesher asserts, writes the errors to a log file, and aborts.

Despite these checks, it is still possible to give the mesher bad input, and additional checks could be added in the future. For example, checks that outer polygons are clockwise and inner polygons are counter-clockwise and no inner polygon is outside it's outer polygon could be added.

Example - Smooth transition for constant size function

This example shows how to specify a constant size function on a polygon that will smoothly transition from the spacing of points along the boundary to the specified constant size. This is specified in the xms::MePolyInput class. The size is specified in the m_constSizeFunction variable and a bias is specified in the m_constSizeBias member. To transition as fast as possible between boundary spacing and the m_constSizeFunction set the bias to 1.0 to transition as slowly as possible set the bias to 0.0. The testing code for this example is in TutMeshingTests::test_Example_ConstantSmooth. The input polygon is shown below.

tutMesh_SimplePolygon_Input.png
Simple Polygon with boundary spacing = 10.0

The test shows having the size transition from the boundary spacing of 10.0 to a size of 1.0 and then a second example where the size transitions to 50.

{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {
{0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 50}, {0, 60}, {0, 70}, {0, 80},
{0, 90}, {0, 100}, {10, 100}, {20, 100}, {30, 100}, {40, 100}, {50, 100}, {60, 100},
{70, 100}, {80, 100}, {90, 100}, {100, 100}, {100, 90}, {100, 80}, {100, 70}, {100, 60},
{100, 50}, {100, 40}, {100, 30}, {100, 20}, {100, 10}, {100, 0}, {90, 0}, {80, 0},
{70, 0}, {60, 0}, {50, 0}, {40, 0}, {30, 0}, {20, 0}, {10, 0}, {0, 0}};
// specify a constant size function
inputPoly.m_constSizeFunction = 1.0;
inputPoly.m_constSizeBias = 0.2;
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// generate the mesh and check the base line
const std::string baseFile = "Example_ConstantSmooth";
tutGenerateAndCompare2dm(input, baseFile);
// now make the polygon grow to a bigger size element
input.m_polys.front().m_constSizeFunction = 50;
const std::string baseFile2 = "Example_ConstantSmooth2";
tutGenerateAndCompare2dm(input, baseFile2);
} // TutMeshingIntermediateTests::test_Example_ConstantSmooth

An image of the two 2d meshs is shown below. The *.2dm file for the output 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_ConstantSmooth_base.2dm and Example_ConstantSmooth1_base.2dm.

tutMesh_ConstantSmooth_Output.png
2d meshs generated by transitioning from boundary spacing to constant size

Example - Generate size function from depth

This example shows how use use xms::meSizeFunctionFromDepth to generate a size function from depth measured at points. The user specifies an array of depths and a min element size and a max element size then an array is filled with the sizes. The testing code for this example is in TutMeshingTests::test_Example_SizeFuncFromDepth.

{
// array of depths
xms::VecDbl depths = {0, 5, 10, 20, 25, 5, 0};
// array for the computed sizes
xms::VecDbl elemSize;
// set the value of the min and max element size
double minElem(2), maxElem(102);
// generate the size array
xms::meSizeFunctionFromDepth(depths, elemSize, minElem, maxElem);
// verify that the sizes are as expected
xms::VecDbl baseElemSize = {2, 22, 42, 82, 102, 22, 2};
TS_ASSERT_DELTA_VEC(baseElemSize, elemSize, 1e-9);
// now create an interpolator to pass along to a mesher
// init the locations of the points used to interpolate
BSHP<xms::VecPt3d> pts(new xms::VecPt3d());
*pts = {{10, 10}, {25, 10}, {10, 25}, {50, 10}, {50, 25}, {50, 50}, {25, 50}};
BSHP<xms::VecInt> tris(new xms::VecInt());
// convert the sizes to a float array for the interpolator
xms::VecFlt sizeFlt(elemSize.size(), 0);
int i(0);
for (auto& d : elemSize)
sizeFlt[i++] = (float)d;
// create an IDW interpolator
BSHP<xms::InterpBase> interp = xms::InterpIdw::New();
interp->SetPtsTris(pts, tris);
// now the interpolator could be used with a mesher
poly.m_sizeFunction = interp;
} // TutMeshingIntermediateTests::test_Example_SizeFuncFromDepth

Example - Smooth a size function

This example shows how to smooth a size function based on geometric proximity so that element creation from the size function will satisfy an element growth/reduction factor. For example, if a size function specifies a nodal spacing of 10 meter elements at one location and 100 meter elements at another location 20 meters away then these constraints conflict. The smoothing function can reduce the 100 or increase the 10 based on the parameters passed to the function. The testing code for this example is in TutMeshingTests::test_Example_SmoothSizeFunc.

{
// create a grid of points
BSHP<xms::VecPt3d> pts(new xms::VecPt3d());
*pts = {{0, 0}, {10, 0}, {20, 0}, {30, 0}, {0, 10}, {10, 10},
{20, 10}, {30, 10}, {0, 20}, {10, 20}, {20, 20}, {30, 20}};
// assign all points a size of 100
xms::VecFlt sizes(pts->size(), 100);
// change the size to 1.0 of the point at 0, 10
sizes[4] = 1;
// create a TrTin class from the array of points
BSHP<xms::VecInt> tris(new xms::VecInt());
BSHP<xms::VecInt2d> adjTris(new xms::VecInt2d());
xms::TrTriangulatorPoints tr(*pts, *tris, &*adjTris);
tr.Triangulate();
BSHP<xms::TrTin> tin = xms::TrTin::New();
tin->SetGeometry(pts, tris, adjTris);
// smooth the size function. The size ratio is set to 0.5. The min element
// size is 1.0. The "anchor type" is 0 (meaning min). This means the minimum
// size will be honored and the other values smoothed from the min.
xms::VecFlt vSmooth;
xms::DynBitset ptFlgs;
xms::meSmoothSizeFunction(tin, sizes, 0.5, 1.0, 0, ptFlgs, vSmooth);
xms::VecFlt baseSmooth = {4.46f, 5.90f, 9.36f, 12.83f, 1.0f, 4.46f,
7.93f, 11.39f, 4.46f, 7.93f, 11.39f, 14.86f};
// ensure the results are as expected
TS_ASSERT_DELTA_VEC(baseSmooth, vSmooth, .1);
} // TutMeshingIntermediateTests::test_Example_SmoothSizeFunc

Example - Spring relaxation

This is another example of how to mesh a single polygon with a hole, and in this example, we use the "spring relaxation" method for relaxing the mesh nodes after the initial point seeding is completed. The testing code for this example is TutMeshingIntermediateTests::test_Example_SpringRelax. A picture of the example is shown below. This example still uses a constant point spacing of 10 along the boundary.

tutMesh_SimplePolygonWithHole_Input.png
Simple Polygon with a hole and boundary spacing = 10.0
{
// The MePolyInput class defines a single polygon to be meshed and which
// consists of one outer polygon and, optionally, inner polygons and some
// other options.
xms::MePolyInput inputPoly;
// Outer polygon points are in clockwise order. Do not repeat the first point.
inputPoly.m_outPoly = {
{0, 10}, {0, 20}, {0, 30}, {0, 40}, {0, 50}, {0, 60}, {0, 70}, {0, 80},
{0, 90}, {0, 100}, {10, 100}, {20, 100}, {30, 100}, {40, 100}, {50, 100}, {60, 100},
{70, 100}, {80, 100}, {90, 100}, {100, 100}, {100, 90}, {100, 80}, {100, 70}, {100, 60},
{100, 50}, {100, 40}, {100, 30}, {100, 20}, {100, 10}, {100, 0}, {90, 0}, {80, 0},
{70, 0}, {60, 0}, {50, 0}, {40, 0}, {30, 0}, {20, 0}, {10, 0}, {0, 0}};
// Inner polygons are in counter clockwise order. Do not repeat the first
// point.
inputPoly.m_insidePolys.push_back(xms::VecPt3d());
inputPoly.m_insidePolys[0] = {{40, 40}, {50, 40}, {60, 40}, {60, 50},
{60, 60}, {50, 60}, {40, 60}, {40, 50}};
inputPoly.m_relaxationMethod = "spring_relaxation";
// The MeMultiPolyMesherIo class holds all the options for generating
// UGrids from polygon data
// The m_polys vector holds the polygons that will be filled with a UGrid.
// This case has only 1 polygon.
input.m_polys.push_back(inputPoly);
// generate the mesh and check the base line
const std::string baseFile = "Example_SpringRelax";
tutGenerateAndCompare2dm(input, baseFile);
} // TutMeshingIntermediateTests::test_Example_SpringRelax

An image of the 2d mesh generated from this example is shown below. The *.2dm file for this 2d mesh can be found at files_xmsmesh/Test/Tutorial_Meshing/Example_SpringRelax_base.2dm.

tutMesh_SpringRelax_Output.png
2d mesh generated from simple polygon with a hole and boundary spacing = 10.0, using spring relaxation

Example - Triangular to quad mesh using Quad Blossom and Bad Quad Remover

This is an experimental feature that is not supported in the public interface.

This example shows how to convert a triangular mesh to a quad mesh using the Quad Blossom algorithm. Then the xms::MeBadQuadRemover class is used to remove narrow, ill-formed quads while still preserving the basic sizes of cells.

There are two arguments to MeQuadBlossom::MakeQuads(). The first is a bool: splitVertices. If this is false, the algorithm will not attempt to match triangles on the boundary that share a boundary point but not an edge; that is, there is at least one other triangle between them that also shares that boundary point. So by setting this argument to false, you may be left with more than one triangle in the mesh, even if the number of boundary edges is even. If this argument is true, it will match such triangles and then turn them into quads by adding a new point on the interior of the mesh and an edge between that point and the boundary point. The two matching boundary triangles will add and share that edge, thus becoming quads. If the number of triangles incident to the boundary point to be split is odd, the new point is placed at the centroid of the middle triangle; if even, it is at the mid-point of the middle edge. The other cells incident to the boundary point will swap to use the new split point instead. So with this argument set to true, you may see some added points in the output ugrid. Internally, the algorithm weights the match on such adjacent boundary triangles lower than any interior edges, so the interior edges will get matched and eliminated before splitting any boundary points to form new edges. If set to false, the output quad cells will just be the result of removing prior interior edges so that pairs of triangles become quads.
The second argument is also a bool: useAngle. There are two internal methods used to associate a weight for each interior edge (between adjacent triangles). Both methods are based on the quad that would be formed by eliminating that edge. If useAngle is true, the weight is based on the interior angles of the quad (and in particular the one farthest from being a right angle. If false, the weight is based on the ratio of the sum of the squares of the lengths of each pair of adjacent sides to the length of their corresponding diagonal squared. Again, it ends up being based on the angle with the maximum deviation from 90 degrees. In both cases, we multiply the floating point weight (that is between 0 and 1) by 1,000 to get an integer weight between 0 and 1,000. (The weights between boundary triangles that might be split are all set to -10.) In general, the closer the quad is to rectangular, the closer the weight is to the maximum, and as the aspect ratio gets larger, the closer the weight gets to zero. They produce slightly different results, but we don't have enough experience yet to recommend one over the other.

Also, note that the algorithm is O(N^3) with respect to the number of points in the mesh. Hence, we provide a function to estimate the runtime. In general, if your mesh has over a 2,000 points, you should split it at convenient boundaries to get below that limit and then run the algorithm on each submesh. You should get no triangles in the result if the sum of all of the boundary edges is an even number and you set splitVertices to true. The main purpose of PreMakeQuads() is to find the edges and return the number of boundary edges, so you can check that it is even. This is demonstrated in the example below.

tutMesh_QuadBlossom_Input.png
Triangular mesh input to Quad Blossom algorithm
{
// read a UGrid from a file.
const std::string path(std::string(XMS_TEST_PATH) +
"Tutorial_Meshing/");
const std::string inputFilePath = path + "Example_QuadBlossom_triangleUGridInput.txt";
BSHP<xms::XmUGrid> ugrid = xms::XmReadUGridFromAsciiFile(inputFilePath); // read from file.
BSHP<xms::MeQuadBlossom> quadBlossom = xms::MeQuadBlossom::New(ugrid);
// PreMakeQuads returns the number of boundary edges of the triangular mesh.
// An even number of boundary edges insures no triangles in the output UGrid.
int nBoundaryEdges = quadBlossom->PreMakeQuads();
TS_ASSERT((nBoundaryEdges & 0x1) == 0);
// The MeQuadBlossom basic algorithm is O(N^3). Check the estimated minutes. If too large,
// Then split the mesh into smaller sub-UGrids, then call MakeQuads on each one.
double minutes = xms::MeQuadBlossom::EstimatedRunTimeInMinutes(ugrid->GetPointCount());
TS_ASSERT(minutes < 2.0);
bool splitVertices = true;
bool useAngle = false;
BSHP<xms::XmUGrid> quadUGrid = quadBlossom->MakeQuads(splitVertices, useAngle);
// Test the quad UGrid generated by the Quad Blossom algorithm
const std::string outFile = path + "Example_QuadBlossom_quadUGrid_out.txt";
xms::XmWriteUGridToAsciiFile(quadUGrid, outFile);
const std::string baseFile = path + "Example_QuadBlossom_quadUGrid_base.txt";
TS_ASSERT_TXT_FILES_EQUAL(baseFile, outFile);
// Use MeBadQuadRemover to remove bad quads from the quad UGrid.
double maxAspect = 0.7;
BSHP<xms::MeBadQuadRemover> badQuadRemover = xms::MeBadQuadRemover::New(quadUGrid);
BSHP<xms::XmUGrid> quadUGridImproved = badQuadRemover->RemoveBadQuads(maxAspect);
// Test the improved quad UGrid returned from the Bad Quad Remover algorithm
const std::string outFile2 = path + "Example_QuadBlossom_quadUGridImproved_out.txt";
xms::XmWriteUGridToAsciiFile(quadUGridImproved, outFile2);
const std::string baseFile2 = path + "Example_QuadBlossom_quadUGridImproved_base.txt";
TS_ASSERT_TXT_FILES_EQUAL(baseFile2, outFile2);
// Use MeBadQuadRemover to remove bad quads from the quad UGrid a second time.
maxAspect = 0.7;
badQuadRemover = xms::MeBadQuadRemover::New(quadUGridImproved);
BSHP<xms::XmUGrid> quadUGridImproved2 = badQuadRemover->RemoveBadQuads(maxAspect);
// Test the improved quad UGrid returned from the Bad Quad Remover algorithm
const std::string outFile3 = path + "Example_QuadBlossom_quadUGridImproved2_out.txt";
xms::XmWriteUGridToAsciiFile(quadUGridImproved2, outFile3);
const std::string baseFile3 = path + "Example_QuadBlossom_quadUGridImproved2_base.txt";
TS_ASSERT_TXT_FILES_EQUAL(baseFile3, outFile3);
} // TutMeshingIntermediateTests::test_Example_QuadBlossom_BadQuadRemover
tutMesh_QuadBlossom_Output.png
Output mesh input after running Quad Blossom algorithm
tutMesh_QuadBlossom_BadQuadRemover_Output.png
Output mesh input after running Quad Blossom and Bad Quad Remover first time
tutMesh_QuadBlossom_BadQuadRemover_Output2.png
Output mesh input after running Bad Quad Remover second time

Running an angle relaxer should improve the mesh even more. As shown, you can remove bad quads several times, but each time will result in larger quads. It isn't clear that several such runs significantly improves the mesh.