# Surface Triangulation¶

The visual resolution of a 3D surface is dependent on the viewer’s interpretation of both the geometry and the color. The color interpretation itself is dependent on the distribution of surface color and the 3D lighting effect.

For many cases, visually smoothing a surface can be made by increasing the number of polyhedral faces
used to represent the surface geometry.
The Surface3DCollection method *triangulate* can be used to increase the number of faces in a mesh,
enhancing the visual resolution for
both geometry and color. A surface object, *surface*, is triangulated as:

```
surface.triangulate(rez)
```

where the single *rez* argument recursively subdivides each face into four faces *rez* times.
In general, a rez of 3 or 4 is sufficient for geometric and color smoothing.
For example, for a rez of 3, each original face will be composed of 64 sub-faces.

The default value for *rez* is 0. For this value, there is no effect on triangulated surface
meshes. Other polygon mesh surfaces will be converted to triangular meshes when res is zero
and triangulated further if rez is greater than zero.

However, for some cases, surface constructions may limit increasing the number of faces when

polygon meshes are predefined on input. Further subdivision of the polygons still will produce ‘flat’ meshes over the original polygons.

meshes are defined with vertex values which are used to assign face values. Matplotlib surface colors are set by polyhedral face values and so colors are not smoothed over faces.

surfaces are defined by a functional definition of geometry that creates anomalous variations using a higher resolution.

In the following sections, methods for increasing the perceived resolution of surfaces are described for
these cases, which are used in conjunction with the *triangulate* method.

## Geometry Smoothing¶

Geometric smoothing is made by linearly approximating the vertex normals across
the triangulated flat surfaces. The vertex normals are then used to calculate the face normals.
The triangulation method does not change the flat polyhedral face geometry. However,
when applying the shade and hilite methods, these effects can be ‘smoothed’ over the
flat subdivided faces by setting the *flat* argument to *False*, i.e.

```
surface.shade(flat=False)
```

and:

```
surface.hilite(flat=False)
```

The default value for the *flat* argument is True. In general, when using both
the shade and hilite methods, the flat arguments are both set to the same value.

The effect of using *triangulate* and *flat=False* on a low resolution surface is
demonstrated in the figure below.

For these examples, geometric mapping was first made to generate the low rez surfaces (the left and middle surfaces). Since this surface is defined mathematically, functional mapping can be used with a starting high rez, without further triangulation. For comparison, this was used for the surface on the right.

By comparing the center and right surfaces, the initial high rez surface provides more visual detail of the geometry compared to the low rez triangulated surface. This occurs even though both surfaces have the same number of polyhedrons. However, simply increasing the rez may not be an option for predefined surface meshes. For such cases, geometric smoothing using triangulation is particularly useful. Consider the following example:

In this case, the predefined mesh was obtained from an obj file, not generated from a functional operation ( see Stanford Bunny ). An additional example is shown in Unstructured coordinates, Smoothed Surface.

Note

Any geometric mapping or clipping, following the use of triangulate, will remove the effect of shading or highlighting using flat=False.

## Color Smoothing¶

Surface coloration is made by assigning a color to each polyhedral face. When colormaps are used, colormap indices are mapped from either face scalar values or from vertex scalar values. Color smoothing over a flat triangulated face is accomplished by linear interpolation of these values over the sub-faces. In this sense, values are smoothed and not colors.

### Face values¶

Face values are represented as colors using the mapping method of a surface object as:

```
surface.map_cmap_from_op(operatioon,cmap)
```

where the *operation* argument is a user-defined function of xyz face-center
coordinates and returns a scalar value. The *cmap* argument is the colormap
to represent the values. Surface faces, following geometric mapping,
will retain the color. This is demonstrated
in the Base Class Geometric Mapping and Face Color Array examples.

To increase the color distribution across ‘flat’ triangular faces, the
*triangulate* method is use **prior** to the color mapping method, as:

```
surface.triangulate(rez)
surface.map_cmap_from_op(operatioon,cmap)
```

If surface methods are called in reverse order, the flat surfaces will visually remain flat, as shown in the following figure.

In general, there would be no need to call the triangulate method following colormapping if the surfaces are to be visually flat, as example Platonic Solids. However, this may be needed when subsquent geometric mappings need to maintain the flat surface colors, for example Base Class Geometric Mapping and Random Grid Geometry .

### Vertex values¶

Vertices can be assigned values in two different ways. First, passing a N X 4 vertCoor argument to the Surface3DCollection constructor (see the Vertex Coordinate Array with Values example).

Or secondly, using the surface object method:

```
surface.set_vertvals(values)
```

where *values* is array or list of N scalar values, with N being the number of surface
vertices (see for example Vertex Value Assignment to a Mesh ).

Once vertex values are assinged, subsequent triangulation will smooth the vertex values over the flat faces. These values can then be mapped to a colormap, cmap, using:

```
surface.map_cmap_from_vertvals(cmap)
```

This is illustrated in the following figure by randomly assigning values to the vertices of a spherical grid geometry and colormapping as:

```
surf = s3d.SphericalSurface.grid(6,10,'d')
vertVals = np.random.uniform(-3,5,len(surf.vertices.T))
surf.set_vertvals(vertVals)
surf.triangulate(rez)
surf.map_cmap_from_vertvals(cmap,'vertex value')
```

with the result:

Once surface coloring is applied, further geometric smoothing of the triangulated flat faces can
be made by setting *flat=False* during shading, as shown below.

Applying both vertex and geometric smoothing to a surface is also shown in the Cosmic Bunny example.

## Polygon Intersection¶

During Matplotlib rendering of a Poly3DCollection, multiple polygons are stacked along the view direction. However, an incorrect rendering is produced for intersecting polygons with the visualization affected by the view direction. The visualization can be improved by triangulating the surface object into small polygons which approximate the resolution of the image.

For example, consider the simple case of a surface object composed of three intersecting triangles: The script below defines the vertices, v, and faces indicies, f, for two such surfaces.

```
v = [ [0,0,0], [1,0,0], [1,1,0], [0,1,0],
[0,0,1], [1,0,1], [1,1,1], [0,1,1] ]
f = [ [1,6,4], [1,3,5], [0,2,7] ]
colors, tks = [ 'C0', 'C1', 'C2' ], [0,.5,1]
surf = s3d.Surface3DCollection(v,f,color=colors)
surfT = s3d.Surface3DCollection(v,f,color=colors).triangulate(6)
```

As seen in the figure below, the visualization is incorrect without triangulation. With triangualtion, the intersection of the smaller triangles is not apparent since the triangle sizes are small compared to the image resolution.

This simple surface example has ‘large’ polyhedrons compared to the figure size, hence the need for a rez value of 6. In general, the triangulate rez value will be smaller for surfaces composed of smaller polyhedrons. Triangulation is particularly needed for composite surfaces where polygon intersections are common in the composite.

## Example python scripts¶

**Figure 1:** Geometry smoothing:

```
import numpy as np
import matplotlib.pyplot as plt
import s3dlib.surface as s3d
def deflate(rtp) :
r,t,p = rtp
scale = 0.3
Rz = np.cos(p)
Rxys = (1-scale)*np.sin(p) + scale*np.cos(4*t)
R = np.sqrt( Rz**2 + Rxys**2)
return R,t,p
# Construct figure, add surface, plot ..........................
rez,btype,trez,minmax = 1, 'dodeca', 4, (-.75,.75)
fig = plt.figure(figsize=(9,3))
fig.text(0.01,0.99,'Figure 1',va='top',ha='left',style='italic',c='b')
for i in range(3) :
ax =fig.add_subplot(131+i, projection='3d', aspect='equal')
ax.view_init(azim=-15)
ax.set(xlim=minmax, ylim=minmax, zlim=minmax)
ax.set_axis_off()
surf = s3d.SphericalSurface(rez,btype)
surf.map_geom_from_op(deflate)
pos = .5
if i==0 :
pos -= .33
title = "rez = "+str(rez)
surf.shade().hilite()
elif i==1 :
title = "rez = "+str(rez) + "\ntriangulate(" + str(trez) + \
"), flat=False"
surf.triangulate(trez).shade(flat=False).hilite(flat=False)
else :
pos += .33
title = "rez = "+str(rez+trez)
surf = s3d.SphericalSurface(rez+trez,btype)
surf.map_geom_from_op(deflate)
surf.shade().hilite()
info = 'faces: ' + str(len(surf.facecenters.T)) +'\nvertices: ' + str(len(surf.vertices.T))
fig.text(pos,0.01,info, ha='center', va='bottom', fontsize='smaller')
ax.set_title(title,fontsize='x-large')
ax.add_collection3d(surf)
fig.tight_layout()
plt.show()
```

**Figure 2:** Face value:

```
import matplotlib.pyplot as plt
import s3dlib.surface as s3d
# Setup surface ................................................
trez, cmap, title = 4, 'jet', [None]*2
zDir = lambda c : s3d.SphericalSurface.coor_convert(c,True)[2]
title[0] = 'triangulate BEFORE\ncolor mapping'
triCmap = s3d.SphericalSurface.grid(6,10,'r')
triCmap.triangulate(trez)
triCmap.map_cmap_from_op(zDir,cmap)
title[1] = 'triangulate AFTER\ncolor mapping'
cmapTri = s3d.SphericalSurface.grid(6,10,'r')
cmapTri.map_cmap_from_op(zDir,cmap)
cmapTri.triangulate(trez) # NOT needed, no effect on visualization
# Construct figure, add surface, plot ..........................
fig = plt.figure(figsize=(6,3))
fig.text(0.01,0.99,'Figure 2',va='top',ha='left',style='italic',c='b')
for i,surface in enumerate([triCmap, cmapTri]) :
ax = fig.add_subplot(121+i, projection='3d', aspect='equal')
ofst = 0.26 if i==0 else 0.67
fig.text(ofst,0.02,title[i], ha='center', va='bottom', fontsize='large')
ax.set_axis_off()
minmax = (-.75,.75) if i==0 else (-.62,.62)
ax.set(xlim=minmax, ylim=minmax, zlim=minmax)
ax.add_collection3d(surface.shade().hilite(.5))
cbar = plt.colorbar(triCmap.cBar_ScalarMappable, ax=ax, shrink=0.7 )
cbar.set_label('face values', rotation=270, labelpad=10)
fig.tight_layout(pad=1)
plt.show()
```

**Figure 3:** Vertex value:

```
import numpy as np
import matplotlib.pyplot as plt
import s3dlib.surface as s3d
# Setup surface ................................................
rez,cmap,seed = 4,'jet', 1
np.random.seed(seed)
surface = s3d.SphericalSurface.grid(6,10,'d')
edges = surface.edges
vertices = surface.vertices
vertVals = np.random.uniform(-3,5,len(vertices.T))
surface.set_vertvals(vertVals)
surface.triangulate(rez)
surface.map_cmap_from_vertvals(cmap,'vertex value')
# Construct figure, add surface, plot ..........................
title = ['random vertex values', 'interpolated face values']
fig = plt.figure(figsize=(6,3))
fig.text(0.01,0.99,'Figure 3',va='top',ha='left',style='italic',c='b')
for i in range(2) :
ax =fig.add_subplot(121+i, projection='3d', aspect='equal')
ofst = 0.26 if i==0 else 0.7
fig.text(ofst,0.02,title[i], ha='center', va='bottom', fontsize='large')
ax.set_axis_off()
minmax = (-.75,.75) if i==0 else (-.6,.6)
ax.set(xlim=minmax, ylim=minmax, zlim=minmax)
if i==0 :
ax.add_collection3d(edges.fade(.1))
ax.scatter(*vertices, c=vertVals, cmap=cmap, s=100,edgecolor='k')
else :
ax.add_collection3d(surface.shade())
cbar = plt.colorbar(surface.cBar_ScalarMappable, ax=ax, shrink=0.7 )
cbar.set_label(surface.cname, rotation=270, labelpad=5)
fig.tight_layout()
plt.show()
```

**Figure 4:** Intersecting triangles:

```
from matplotlib import pyplot as plt
import s3dlib.surface as s3d
v = [ [0,0,0], [1,0,0], [1,1,0], [0,1,0],
[0,0,1], [1,0,1], [1,1,1], [0,1,1] ]
f = [ [1,6,4], [1,3,5], [0,2,7] ]
colors, tks = [ 'C0', 'C1', 'C2' ], [0,.5,1]
surf = s3d.Surface3DCollection(v,f,color=colors)
surfT = s3d.Surface3DCollection(v,f,color=colors).triangulate(6)
vE,iE = [ [1,0,1], [1,1,1], [0,1,1], [1,1,0] ], [ [0,1,2],[1,3]]
title = ['INCORRECT\nvisualization', 'Intersecting Polygons\nusing triangulate(6)']
fig = plt.figure(figsize=(6,3))
fig.text(0.01,0.99,'Figure 4',va='top',ha='left',style='italic',c='b')
for i,surface in enumerate([surf,surfT]) :
ax = fig.add_subplot(121+i, projection='3d', aspect='equal', focal_length=0.5)
pos = 0.25 if i==0 else 0.75
fig.text(pos,0.9,title[i],ha='center',va='center')
ax.set(xlabel='X', ylabel='Y', zlabel='Z',
xticks=tks, yticks=tks, zticks=tks )
ax.view_init(25,14)
ax.add_collection3d(surface )
ax.add_collection3d( s3d.ColorLine3DCollection(vE,iE,color='0.7',lw=1) )
fig.tight_layout(pad=3)
plt.show()
```