Here is a different approach. The idea is to first generate a second order triangle mesh. Next a center coordinate is added in every triangle. Then we split every triangle into three first order quads making use of the newly added center coordinate and the initial second order triangle incidents.
So we add a node in the center of the element and create the quad elements {c,6,1,4}, {c,4,2,5} and {c,5,3,6}.
Start with an initial triangle mesh:
Needs["NDSolve`FEM`"]
m = ToElementMesh[
DiscretizeGraphics[
GraphicsComplex[{{0, 4}, {5, 4}, {5, 0}, {8, 0}, {8, 8}, {0, 8}},
Polygon[{1, 2, 3, 4, 5, 6}]]]];
Two helper functions that work for second order triangle mesh elements. If one want to extend this to 3D then you'd need to add a tetToHex function.
getMidEleCoords = Compile[{{eleCoords, _Real, 2}},
Mean[eleCoords], RuntimeAttributes -> {Listable}];
triToQuad = Compile[{{triInci, _Integer, 1}, {midInci, _Integer, 0}}, {
{midInci, triInci[[6]], triInci[[1]], triInci[[4]]},
{midInci, triInci[[4]], triInci[[2]], triInci[[5]]},
{midInci, triInci[[5]], triInci[[3]], triInci[[6]]}
}
, RuntimeAttributes -> {Listable}
];
getConverterCode[TriangleElement] := triToQuad;
getNumOfResEle[TriangleElement] := 3
getConvertedEleType[TriangleElement] := QuadElement
First, we compute the new coords within the triangle/tet elements:
coords = m["Coordinates"];
numOfCoords = Length[coords];
meshEle = m["MeshElements"];
inci = ElementIncidents[meshEle];
markers = ElementMarkers[meshEle];
eleCoords = GetElementCoordinates[coords, #] & /@ inci;
midEleCoords = getMidEleCoords[eleCoords];
numOfMidCoords = Length /@ midEleCoords;
temp = Join[{numOfCoords}, numOfMidCoords];
temp = FoldList[Plus, temp] + 1;
midEleInci = MapThread[Range, {Most[temp], Rest[temp - 1]}];
newCoords = Join[coords, Join @@ midEleCoords];
(*Max[midEleInci]===Length[newCoords]*)
The we generate the new elements:
newEle = Table[
Block[{thisMeshEle, thisType, thisInci, thisMarker, converter,
newEle, newEleMarkers},
thisMeshEle = meshEle[[i]];
thisType = Head[thisMeshEle];
thisInci = ElementIncidents[thisMeshEle];
thisMarker = ElementMarkers[thisMeshEle];
converter = getConverterCode[thisType];
newEle = Join @@ converter[thisInci, midEleInci[[i]]];
newEleMarkers =
Join @@ Transpose[
ConstantArray[thisMarker, getNumOfResEle[thisType]]];
getConvertedEleType[thisType][newEle, newEleMarkers]
]
, {i, Length[meshEle]}];
Generate a quad mesh:
mq1 = ToElementMesh["Coordinates" -> newCoords,
"MeshElements" -> newEle]
(* ElementMesh[{{0., 8.}, {0., 8.}}, {QuadElement["<" 1338 ">"]}] *)
Look at the mesh:
mq1["Wireframe"]
This is now a first order mesh:
mq1["MeshOrder"]
1
One can use MeshOrderAlteration[mq1, 2]
to then subsequently generate a second order mesh.
Histogram[Join @@ mq1["Quality"]]
Quality looks OK, but a smoothing of some kind (e.g. Laplacian smoothing) might be well worth trying out.
(Update: There is a follow up question that discusses the idea of Laplacian smoothing. Spoiler: Does not look like it's worth the trouble)
Solve a model problem over the mesh:
sol = NDSolveValue[{-Laplacian[u[x, y], {x, y}] == 1,
DirichletCondition[u[x, y] == 0, True]}, u, {x, y} \[Element] mq1];
Plot3D[sol[x, y], {x, y} \[Element] sol["ElementMesh"]]
Since this will generate three quad elements for every triangle the mesh cell measure is lower than what it needs to be, one could adjust for that by making use of "MaxCellMeasure" in the initial triangle mesh.
Hope this helps.