Illumination Orientation¶
Figure orientation in 2D is generally of little concern. Usually, plots are oriented with independent variables on the horizontal axis, x-axis, and dependent ones on the vertical axis, y-axis. Then viewed normal to plane.
For 3D surfaces, visualizations are controlled by
- lighting direction relative to the coordinate axes
- viewing position relative to the coordinate axes
- lighting direction relative to the viewer
- surface orientation relative to the coordinate axes
Control of the surface orientation is discussed in the Object Rotation guide.
Coordinate Axes Lighting¶
To visualize 3D surface and line objects, apparent shading can be added to the object using the method:
object.shade(direction)
where direction is an xyz vector in the direction of apparent light source relative to the coordinate axes. If no direction vector is provided, the default lighting vector will be [1,0,1]. In addition, for a surface object, the highlighting method can also be used, as:
surface.hilite(direction)
to enhance the three-dimensional effect. Further details on the additional arguments for these methods are provided in the Shading, Highlighting and Color Mapped Normals and Open Surfaces : Shading and Highlighting guides.
Coordinate Views¶
Standard 3D axis views are controlled by the Matplotlib Axes3D view_init method:
axes.view_init(elev, azim)
where elev and azim are the elevation and azimuthal viewing angle relative to the coordinate axis. The azim controls the rotation about the z-axis. The elev sets the z-axis tilt forward and back from the view.
Since restricted to controlling views using these two arguments, the omission of a third rotation in 3D doesn’t permit the z-axis to be tilted to the right or left in the view. The transform method discussed in the next section permits viewing the surface object from this perspective by rotating the surface in addition to the view. The following plots show the effect of shading and highlighting for various view orientations, with the illumination direction of [0,1,1].
The default values for elev and azim are 30 and -60, respectively in degree units, which is used if this method is not called. In addition, three planes normal to the axis are shown by default. S3Dlib provides a method to show an xyz coordinate axis system in the 3D axes visualization, called as:
s3d.setupAxis( axes, **kwargs )
where the Matplotlib 3D axis is passed as the first argument and the keyword arguments are:
key | assignment values | control | default |
---|---|---|---|
length | single or 3 value list of numbers | coordinate axis lengths | 1.5 |
offset | single or 3 value list of numbers | axis offset from the orign | 0.0 |
labels | single or 3 value list of numbers | axis labels | [‘X’,’Y’,’Z’] |
width | number | axis line width | 2 |
color | color | axis colors | ‘black’ |
negaxis | boolean | display negative axis | False |
alr | float (0.0 to 1.0) | axis head to length ratio | 0.2 |
When viewing surfaces, particularly mathematical function representations, a ‘standard’ coordinate system is usually shown with the x and y axis pointing to the left and right, with the z-axis pointing upward. S3Dlib provides a method to initialize the plot for s ‘standard’ view using the method:
s3d.standardAxis( axes )
This method uses the same argument list as the previous method. When this method is called, the axis view is changed using view_init(30,30) and the axis set off. In addition, the minimum and maximum x, y, and z-axis are set to -1 and 1, which is consistent with the S3Dlib object size normalizations.
The default axes setup for the three methods is shown in the plots below.
Viewer Lighting¶
The perception of 3D surfaces from a 2D image is accomplished by ‘simulating’ an illumination projection based on surface normals. S3Dlib illumination sources are referenced relative to the coordinate axis, as discussed in the Shading, Highlighting and Color Mapped Normals guide. The Matplotlib view parameters of elevation and azimuth are also referenced relative to the coordinate axis.
As coordinate views and object orientations are changed from the default, it may be useful to have an illumination source that is referenced to the viewer, not the coordinate axis. This is particularly needed when animations are required to be ‘perceived as’ the object orientation being changed, not the viewer orientation changing. This was achieved in the Platonic Solids animation example. In that case, the objects are perceived as rotating, not the viewer moving around the objects. However, the opposite was actually being calculated with the illumination source position changing with the view.
So, the two types of perceptions of the same object are:
- Stationary viewer - Illumination source referenced to the viewer.
- Stationary object - Illumination source referenced to the coordinate system.
Using a sunset analogy, is the sun going down or are you rotating backward? It’s the same image. Kepler, Copernicus and Galileo made a perceptual leap.
The illumination source relative to the viewer can be determined using the function:
reldir = rtv(direction,elev,azim)
where direction is a 3D vector in xyz coordinates. The parameters, ‘elev’ and ‘azim’ are the view orientations. Using the ‘reldir’ for the direction in the shade and hilite methods, the light source will always appear to come from the same direction, independent of the coordinate elev and azim view. In the following figures, a direction argument of [1,1,1] was used for all views.
A good example of using the relative illumination source is shown in the Python Cube animation example where both elev and azim parameters are varied from frame to frame.
Source Code¶
Source code for the figures is given below:
import numpy as np
from matplotlib import pyplot as plt
import s3dlib.surface as s3d
#.. Guides: Illumination Orientation
# Figure 0 : axis coor view =============================================================
minmax=(-1,1)
title = [ 'default', 'setupAxis( axes )', 'standardAxis( axes )']
karg = { 'width':1.5, 'alr':0.13, 'negaxis':True}
fig = plt.figure(figsize=(8,2.8))
fig.text(0.9,0.01,'Figure 0', fontsize='x-small')
for i in range(3) :
ax = fig.add_subplot(131+i, projection='3d')
ax.set(xlim=minmax, ylim=minmax, zlim=minmax)
ax.set_title(title[i])
if i==2 : ax.set_axis_off()
if i==1 : s3d.setupAxis(ax,**karg)
if i==2 : s3d.standardAxis(ax,**karg)
fig.tight_layout(pad=2)
# Figure 1 & 2 : illuminated surfaces ===================================================
isRelative = True # two different figures generated with bool
fN = "2" if isRelative else "1"
rez,minmax,viewCoor = 3, (-1,1), [ [30,-60], [30,-10], [30,30], [40,140] ]
fig = plt.figure(figsize=(7.762,2.116))
fig.text(0.9,0.01,'Figure '+fN, fontsize='x-small')
for i,vCoor in enumerate(viewCoor) :
ax = fig.add_subplot(141+i, projection='3d')
ax.set_aspect('equal')
ax.set(xlim=minmax, ylim=minmax, zlim=minmax)
color = 'yellowgreen' if isRelative else 'lightseagreen'
axcolor = 'firebrick' if isRelative and i==2 else 'black'
absdir = [1,1,1] if isRelative else [0,1,1]
illum = s3d.rtv(absdir,*vCoor) if isRelative else absdir
s3d.standardAxis(ax, length=[2,1.7,1.7], width=2, color=axcolor, offset=1.0, negaxis=True)
ax.text(0,0,-2,str(vCoor), color = 'black', ha='center', va='center')
ax.set_axis_off()
ax.view_init(*vCoor)
s = s3d.SphericalSurface(rez,facecolor=color)
s.shade(direction=illum,contrast=0.7).hilite(direction=illum,focus=2)
ax.add_collection3d(s)
fig.tight_layout()
# =======================================================================================
plt.show()