# Calculate surface normals at the boundary of a Graphics3D object

How do I go about calculating and plotting the surface normals at the boundary of a Graphics3D object?

For example, consider this custom-defined ParametricPlot3D with boundaries (see Get Graphics3D object for only part of a cone):

boundedOpenCone[centre_, tip_, Rc_, vec1_, vec2_, sign_] :=
Module[{v1, v2, v3, e1, e2, e3},
(* function to make 3d parametric plot of the section of a cone \
bounded between two vectors: tvec1 and tvec2*)

{v1, v2, v3} = # & /@ HodgeDual[centre - tip];
e1 = Normalize[v1];
e3 = Normalize[centre - tip];
e2 = Cross[e1, e3];

ParametricPlot3D[
s*tip + (1 - s)*(centre + Rc*(Cos[t]*e1 + Sin[t]*e2)), {t, 0,
2 \[Pi]}, {s, 0, 1}, Boxed -> False, Axes -> False, Mesh -> None,
RegionFunction ->
Function[{x, y, z},
RegionMember[
HalfSpace[sign*Cross[vec1 - tip, vec2 - tip], tip], {x, y, z}]],
PlotStyle -> ColorData["Rainbow"][1]]
]

vec1 = {1, 0, 0}; vec2 = (1/Sqrt[2])*{1, 1, 0};
coneTip = {0, 0, 3};
cvec = {0, 0, 0};
Rc = Norm[vec1 - cvec];

boundedOpenCone[cvec, coneTip, Rc, vec1, vec2, -1];


Some great code for finding the normals everywhere on the surface can be found here: Plot of gradient over a surface

But I would like to get a list of the surface normal vectors, and plot them, only along the boundary of the domain.

• Definitions of o, tvec1, tvec2 are missing. May 22, 2021 at 3:13
• Just corrected it.
– ap21
May 22, 2021 at 3:17
• May 22, 2021 at 3:47
• @xzczd Thanks, that is perfect for getting the normals. Now I just need to figure out how to plot get the list of the normal vectors only along the boundary? And to plot them as well.
– ap21
May 22, 2021 at 3:59
• Possible duplicate: mathematica.stackexchange.com/questions/130226/… May 22, 2021 at 4:46

First of all, we need 2 more options in boundedOpenCone. The option BoundaryStyle -> Automatic creates a Line on the boundary so we can easily locate the coordinates of point on the boundary. PlotPoints -> 100 isn't actually necessary, but will make the resulting boundary smoother.

boundedOpenCone[centre_, tip_, Rc_, vec1_, vec2_, sign_] :=
Module[{v1, v2, v3, e1, e2,
e3},(*function to make 3d parametric plot of the section of a cone bounded between \
two vectors:tvec1 and tvec2*){v1, v2, v3} = # & /@ HodgeDual[centre - tip];
e1 = Normalize[v1];
e3 = Normalize[centre - tip];
e2 = Cross[e1, e3];
ParametricPlot3D[
s*tip + (1 - s)*(centre + Rc*(Cos[t]*e1 + Sin[t]*e2)), {t, 0, 2 \[Pi]}, {s, 0, 1},
Boxed -> False, Axes -> False, Mesh -> None, BoundaryStyle -> Automatic,
RegionFunction ->
Function[{x, y, z},
RegionMember[HalfSpace[sign*Cross[vec1 - tip, vec2 - tip], tip], {x, y, z}]],
PlotPoints -> 100, PlotStyle -> ColorData["Rainbow"][1]]]

vec1 = {1, 0, 0}; vec2 = (1/Sqrt[2])*{1, 1, 0};
coneTip = {0, 0, 3};
cvec = {0, 0, 0};
Rc = Norm[vec1 - cvec];

pplot = boundedOpenCone[cvec, coneTip, Rc, vec1, vec2, -1];


Then we modify normalsShow from the document of VertexNormals a little to preserve only the normals on the boundary:

boundarynormals[g_Graphics3D] :=
Module[{pl, vl, boundaryindexlst = Flatten@Cases[g, Line[a_] :> a, Infinity]},
{pl, vl} = First@Cases[g,
GraphicsComplex[pl_, prims_, VertexNormals -> vl_,
opts___?OptionQ] :> {pl, vl}\[Transpose][[boundaryindexlst]]\[Transpose],
Infinity];
Transpose@{pl, pl + vl/3}];

vectors = boundarynormals@pplot;



• And if I understand correctly, each element of vectors contains two 3-vectors, right? One each for the starting and ending point of the vector?
– ap21
May 22, 2021 at 5:23
• Is it possible to label the boundary points in the image above using boundaryindexlst? How would I do so?
– ap21
May 22, 2021 at 5:28
• @ap21 Your understanding for vectors is right. As to labeling, check document of Callout. May 22, 2021 at 6:01
• Another question please. In the reference you cited, and from which you got your base answer, there is another response for showing the normals at the vertices: Normal[g] /. p : Polygon[c_, ___, VertexNormals -> vn_, ___] :> {p, Black, MapThread[Line[{##}] &, {c, c + vn/3}]}. How would you modify this to incorporate the boundary selection?
– ap21
May 22, 2021 at 7:37
• I ask because boundarynormals[] as defined doesn't work for a Triangle[], while the Normal[g] definition works. Could you please make it work for a Triangle[]?
– ap21
May 22, 2021 at 7:43
dg = DiscretizeGraphics[pplot];


Use the property dg["ConnectivityMatrix"[1, 2]]["AdjacencyLists"] to get edge-face connectivity and get indices of edges connected to a single face (these are the boundary edges of the surface).

boundaryedgeindices =  Flatten @ Position[

HighlightMesh[dg, Style[{1, boundaryedgeindices}, Thick, Red]]


Use the undocumented function RegionMeshMeshCellNormals to get the normals:

boundaryedges = MeshPrimitives[dg, {1, boundaryedgeindices}];
boundaryedgenormals = RegionMeshMeshCellNormals[dg, {1, boundaryedgeindices}];


Show boundary edges and their normals:

boundaryEdgesAndNormals = Graphics3D[MapThread[
{AbsoluteThickness[1], #, RandomColor[],Line[{Mean@#[[1]], Mean@#[[1]] + .2 #2}]} &,
{boundaryedges, boundaryedgenormals}]]


Show together with the surface:

Show[pplot, boundaryEdgesAndNormals, ImageSize -> Large, PlotRange -> All]


Alternatively, we can use polygons (instead of edges) at the boundary of the surface and their normals:

boundarypolygonindices = Flatten@
Select[Length @ # == 1&]@dg["ConnectivityMatrix"[1, 2]]["AdjacencyLists"];

DiscretizeGraphics[Graphics3D[
MeshPrimitives[dg, {2, boundarypolygonindices}]],
PlotTheme -> "FaceNormals", ImageSize -> Large]


Note: With this approach we can only get the direction of normals, since the vectors returned by RegionMeshMeshCellNormals are normalized

MinMax[Norm /@ boundaryedgenormals]

 {1., 1.}