# How to convert a surface into a solid

Context

I am interested in converting surfaces into solids (so I can make a 3D mesh out of them using ToElementMesh)

Say I have the following cool surface

f[t_] := With[{s = 3 t/2}, {(2 + Cos[s]) Cos[t], (2 + Cos[s]) Sin[t], Sin[s]} - {2, 0, 0}]
v1[t_] := Cross[f'[t], {0, 0, 1}] // Normalize
v2[t_] := Cross[f'[t], v1[t]] // Normalize
g[t_, θ_] := f[t] + (Cos[θ] v1[t] + Sin[θ] v2[t])/2
gr = ParametricPlot3D[Evaluate @ g[t, θ], {t, 0, 4 Pi}, {θ, 0, 2 Pi},
Mesh -> None, MaxRecursion -> 4, Boxed -> False, Axes -> False];


which I pinched from this answer. Question

How would like to make a solid out of what is in it?

I have a hint that there is a simple answer to this problem in the context of the new computational geometry tools of Mathematica 10 but I have not found it.

In some sense I am after the opposite of BoundaryMesh

• Try DiscretizeGraphics – user21 Jun 11 '15 at 16:23
• In:= gr // DiscretizeGraphics Out= EmptyRegion; so its a bit disapointing – chris Jun 11 '15 at 16:23
• You could convert it to a ParametricRegion and then use ToElementMesh - some examples are in the documentation, see also the tutorial to element mesh generation. – user21 Jun 11 '15 at 16:33

## 3 Answers

### Method 1: Construct mesh elements manually

We can triangulate a periodic quad-lattice on the surface:

n = {180, 20};  (* number of points in each direction *)
pts = Table[
g[4. Pi/n[] t, 2. Pi/ n[] θ], {t, n[]}, {θ, n[]}];

idcs = {{{1, 2, 4}, {1, 4, 3}}, {{1, 2, 3}, {2, 4, 3}}};  (* for a diamond pattern *)
tri = 1 +
Array[Function[quad, quad[[#]] & /@ idcs[[Mod[+##, 2, 1]]]][Tuples[
{Mod[#1 + {-1, 0}, Dimensions[pts][]],
Mod[#2 + {-1, 0}, Dimensions[pts][]]}].{Length[First@pts], 1}] &,
Most@Dimensions[pts]];

Needs["NDSolveFEM"].
bmesh = ToBoundaryMesh[
"Coordinates" -> Flatten[pts, 1],
"BoundaryElements" -> {TriangleElement[Flatten[tri, 2]]}
]

emesh = ToElementMesh[bmesh]
(*
ElementMesh[{{-4.86396, 1.5}, {-3.32664, 3.32664}, {-1.5, 1.5}},
{TetrahedronElement["<" 21544 ">"]}]
*)

MeshRegion@emesh Notes:

Tuple generates the indices of the quadrilaterals in each row (#1) & column (#2), wrapping around at the end of the domain to close up the tube:

Tuples[{Mod[#1 + {-1, 0}, Dimensions[pts][]],
Mod[#2 + {-1, 0}, Dimensions[pts][]]}]


The dot product with {Length[First@pts], 1} converts a {row, column} pair to the index of the corresponding point in pts.

We triangulate the quadrilaterals by alternating which diagonal is used. The variable idcs contains two lists of two triangles, representing both ways. The integers themselves are indices of the quadrilateral (given by Tuples[..].{Length[..], 1} above).

idcs = {{{1, 2, 4}, {1, 4, 3}},   (* 1-4 diagonal *)
{{1, 2, 3}, {2, 4, 3}}};  (* 2-3 diagonal *)


### Method 2: Mesh the domain and apply parametrization

This takes more advantage of FEM/ElementMesh capabilities. First mesh the domain. Then apply g to map domain mesh coordinates onto to the surface. Use these coordinates and the domain mesh to construct a boundary mesh on the surface. Finally, use ToElementMesh to construct a mesh of the solid.

 Needs["NDSolveFEM"]

tscale = 4; θscale = 0.5;                (* scale roughly proportional to speeds *)
dom = ToElementMesh[FullRegion, {{0, tscale}, {0, θscale}},         (* domain *)
MaxCellMeasure -> {"Area" -> 0.001}];
coords = g[4 Pi #1/tscale, 2 Pi #2/θscale] & @@@ dom["Coordinates"];   (* apply g *)
bmesh2 = ToBoundaryMesh[
"Coordinates" -> coords,
"BoundaryElements" -> dom["MeshElements"]
];
emesh2 = ToElementMesh@ bmesh2
(*
ElementMesh[{{-4.86407, 1.5}, {-3.32362, 3.32362}, {-1.49991, 1.49991}},
{TetrahedronElement["<" 5581 ">"]}]
*)

MeshRegion@ emesh2 Note: I thought I would have to glue the boundaries together by hand, but ToBoundaryMesh seems to handle it for me. :D

• Thanks! I like method 2 better. – chris Jun 12 '15 at 7:02
• @MichaelE2...learned such a lot from this +1, of course...thanks :) – ubpdqn Jun 12 '15 at 7:24
• @chris You're welcome. I like method 2 better, too. But I came up with it second and after some time had passed. I was afraid I'd have to remap coordinates & indices to glue the boundaries together. – Michael E2 Jun 12 '15 at 14:36
• Is there a way to achieve the same thing if there's no known equation of the surface? – RunnyKine Jul 10 '15 at 21:43
• @RunnyKine If the surface is not given by equations, how is it given? (If it's already a valid boundary mesh, then you're in business, aren't you? If it's self-intersecting, then you're in trouble. If it's not closed, then perhaps you could stick on a patch. The ParametricPlot3D has the last problem and maybe the first.) – Michael E2 Jul 11 '15 at 0:34

Here is an attempt to use MichaelE2's method 2 but only using built-in functions with no need to load the FEM package.

tscale = 4; θscale = 0.5;
domain = DiscretizeRegion[FullRegion, {{0, tscale}, {0, θscale}},
MaxCellMeasure -> {"Area" -> 0.0005}]
coords = g[4 Pi #1/tscale, 2 Pi #2/θscale] & @@@ MeshCoordinates[domain];
(* This is the same g defined in the question *)
mr = MeshRegion[coords, MeshCells[domain, 2]];


Here is where it gets tricky with built-in functions. I first convert the MeshRegion mr to a Graphics3D object using Show then discretize the boundary and finally use TriangulateMesh to get a solid. BoundaryMeshRegion seems to fail here, hence, the workaround.

tm = TriangulateMesh @ BoundaryDiscretizeGraphics @ Show @ mr MeshCellCount[tm, 3] > 0    (* check that there are 3D cells *)


True

• Nice! FEM has become a bit like black magic between undocumented functions, failing ones etc… :-) – chris Jul 21 '15 at 6:36
• @user21 Why does BoundaryMeshRegion[coords, MeshCells[domain, 2]] complain about a boundary not being closed? – RunnyKine Jul 21 '15 at 11:02
• @RunnyKine This is the message I get : BoundaryMeshRegion::bsuncl: "The boundary surface is not closed because the edges Line[{{719,1},{97,719},{1,97}}] only come from a single face." – Anton Antonov Apr 3 '16 at 14:11
• @AntonAntonov. I'm guessing you're using a much newer version of Mathematica. A lot of things that worked with these region functions in previous versions are now broken. If you check this site you'll see I've found quite a few. – RunnyKine Apr 5 '16 at 4:06
• @RunnyKine Yes, I am using the newest version. Thank you for your clarification. I like that you programmed Michael E2's approach with built-in functions. – Anton Antonov Apr 5 '16 at 4:31

For a closed surface such as this one, a slight modification of the function MakeTriangleMesh[] in this answer can be used:

MakeTriangleMesh[vl_List, {closedu : (True | False) : False,
closedv : (True | False) : False}, opts___] :=
Module[{dims = Most[Dimensions[vl]], v = vl, idx},
idx = Partition[Range[Times @@ dims], Last[dims]];
If[TrueQ[closedu],
v = Most[v]; idx = Append[Most[idx], First[idx]]];
If[TrueQ[closedv],
v = Most /@ v; idx = Composition[Append[#, First[#]] &, Most] /@ idx];
BoundaryMeshRegion[Apply[Join, vl],
Triangle[Flatten[Apply[{Append[Reverse[#1], Last[#2]],
Prepend[#2, First[#1]]} &,
Partition[idx, {2, 2}, {1, 1}], {2}], 2]], opts]] /;
ArrayQ[vl, 3]

With[{m = 101, n = 31},
MakeTriangleMesh[N[Table[Evaluate @ g[t, θ],
{t, 0, 4 π, 4 π/(m - 1)}, {θ, 0, 2 π, 2 π/(n - 1)}]],
{True, True}]] Compute the volume:

Volume[%]
24.674785447032324
`