Camera Placement: Viewing a House Part 1

This notebook illustrates how to place a camera in world coordinates. To make the visualization more complete, a simple house model is included in the world coordinates. This notebook provides a visualization of the canoncial view volume.

Ross Beveridge September 6, 2018

As will be common in these notebooks, the next sequence of commands configure options for running the notebook such as how to display math, etc.

In [1]:
%display latex
latex.matrix_delimiters(left='|', right='|')
latex.vector_delimiters(left='[', right=']')

To get started let us create a 3D polygonal house model. As you begin to think about 3D modeling it would be valuable to experiment a bit with this code. As is often the case with languages like Python, there is more going on in these few lines of code than you might at first appreciate.

In [2]:
VVL = Matrix(ZZ, ([0,0,30,1],[0,10,30,1],[8,16,30,1],[16,10,30,1],[16,0,30,1],[0,0,54,1],
                  [0,10,54,1],[8,16,54,1],[16,10,54,1],[16,0,54,1]));
VVL = VVL.transpose();
houseFront = (0,1,2,3,4); houseBack = (5,6,7,8,9); 
wallLeft = (0,1,6,5); wallRight = (4,3,8,9); 
roofLeft = (1,2,7,6); roofRight = (3,2,7,8); Floor = (0,4,9,5);

An array of vertices

Perhaps the first thing to notice about this example is the way in which vertices are expressed. Namely, in a 4 x N matrix where N is the number of vertices; N = 10 for the house.

In [3]:
pretty_print("VVL = ", VVL)

Named Faces

Next, faces of the house are specifed as lists of vertex indices. Note vertex index counting starts at zero. In this example, since it is meant to be read by people, the faces are named.

Python provides excellent ways to enumerate the vertices of an individual face. So, for example, consider the following code that provides a list of 3D points representing the vertices of a face. In general we are begining to see that one representation may not serve all our needs. For example, the 4 x N matrix is excellent down the road for applying 3D transformations to whole sets of points. However, the SageMath graphic cocde wants tuples of tuples. Hence, consider carefully the following code since it will come up again when creating SageMath Graphics Objects.

In [4]:
FFL = [[VVL[i,j] for i in range(3)] for j in houseFront]
pretty_print("FrontFaceList = ", FFL)

As promised, now we construct a tuple of graphics objects for later drawing using the technique just illustrated for the front face of the house.

In [5]:
houseSidesWorld = [
   polygon3d([[VVL[i,j] for i in range(3)] for j in houseFront], color=Color('#006633'), alpha=0.7),
   polygon3d([[VVL[i,j] for i in range(3)] for j in houseBack],  color=Color('#3300cc'), alpha=0.7),
   polygon3d([[VVL[i,j] for i in range(3)] for j in wallLeft],   color=Color('#660066'), alpha=0.7),
   polygon3d([[VVL[i,j] for i in range(3)] for j in wallRight],  color=Color('#663300'), alpha=0.7),
   polygon3d([[VVL[i,j] for i in range(3)] for j in roofLeft],   color=Color('#cc3366'), alpha=0.7),
   polygon3d([[VVL[i,j] for i in range(3)] for j in roofRight],  color=Color('#cc6633'), alpha=0.7),
   polygon3d([[VVL[i,j] for i in range(3)] for j in Floor],      color=Color('#666666'), alpha=0.7),
   ];

Creating interactive 3D visualizations is one of the strengths of SageMath. So that you may better understand these notebooks, consider the following single line of code used to 'draw' in 3D the house model. First, the addition operator is being overloaded for graphics objects. Therefore, the summation takes the tuple of 3D polygons and turns it into a summation. At first this take some getting used to since of course this does not represent addition in any standared arithmetic sense. However, it does exactly what we desire in that it creates a set of objects related through 'addition' that the command show in turn will display. The second argument is important to keep relative size along all three axes the same.

In [6]:
houseSidesWorld
Out[6]:
In [7]:
show(sum(houseSidesWorld),aspect_ratio=(1,1,1),viewer='jmol')

Specifying a Camera

There is a lot that goes into placing a camera in a scene. Here is the complete process illustrated.

In [8]:
var('ex', 'ey', 'ez');              # Eye position in the world, also focal point position.
var('lx', 'ly', 'lz');              # Lookat position in the world. 
var('upx', 'upy', 'upz');           # The up vector in the world coordinates.
var('right','left','top','bottom'); # View Volume Sides
var('near', 'far');                 # Distance to the near and far clipping planes.
var('width', 'height');             # Number of pixels horizontal and vertical
# Setup specific Camera
ex = 8;   ey = 8; ez = 100;   # World origin same as camera
lx = 8;   ly = 8;   lz = 54;    # Point toward the positive Z axis
upx =0;  upy = 1;   upz = 0;    # Let the world y axis represent UP
near  = -30; far    = -175;      # The near and far clipping planes 
left  = -30; right  =  30; 
top   =  20; bottom = -20; 
width =   2; height = 2; 
# Build camera system origin and axes in world coordinates
EV = vector(SR, 3); EV[0] = ex;  EV[1] = ey;  EV[2] = ez;
LV = vector(SR, 3); LV[0] = lx;  LV[1] = ly;  LV[2] = lz;
UP = vector(SR, 3); UP[0] = upx; UP[1] = upy; UP[2] = upz; 
WV = EV - LV; WV = WV / WV.norm();
UV = UP.cross_product(WV); UV = UV / UV.norm();
VV = WV.cross_product(UV);

There is a lot going on in the following code. More than can be completely covered in lecture. That said, we will spend considerable time on this in lecture and you should study each and every line until you are comfortable.

Here are just a few things to watch closely. First, entities we know are vectors are placed in the figure at point which 'make sense'. Pay attention to this choice. For example, the unit length vectors defining the X, Y and Z axis of the camera are drawn off the Eye point and also with exagerated magnitude. Also pay attention to the manner in which the four points that bound the near clipping plane, also equally well thought of as the bounded image plane, are constructed. Namely, leave the eye point in the direction of the inverted view plane normal W an amount specified by near. Mechanically, this happens because near is specified with a negative value. Next, the corners are obtained by moving along the basis vectors for the image plane's horizontal and vertical axes. In other words, U and V.

Finally, think about the 'fudge' factor used to move from the corners of the image plane at the near clipping plane to the corners at the far clipping plane. You will observe that it has the desired effect - the plane is drawn properly. Less obvious until you work it out is why this fudge factor is needed and further how it results from a simple dot product; in other words no trigonometry.

In [9]:
def frustum(): 
    p00n = EV + (near * WV) + (left * UV) + (bottom * VV);
    p01n = EV + (near * WV) + (left * UV) + (top * VV);
    p10n = EV + (near * WV) + (right * UV) + (bottom * VV);
    p11n = EV + (near * WV) + (right * UV) + (top * VV);
    ray00n = p00n - EV;  ray00n = (1/ray00n.norm())*ray00n;
    ray01n = p01n - EV;  ray01n = (1/ray01n.norm())*ray01n;
    ray11n = p11n - EV;  ray11n = (1/ray11n.norm())*ray11n;
    ray10n = p10n - EV;  ray10n = (1/ray10n.norm())*ray10n;
    fudge = (1.0 / ray00n.dot_product(-1 * WV));
    delta = fudge * (near-far);
    p00f   = p00n + delta * ray00n;
    p01f   = p01n + delta * ray01n;
    p11f   = p11n + delta * ray11n;
    p10f   = p10n + delta * ray10n;
    return [polygon3d([p00n, p01n, p11n, p10n], color=Color('darkgreen'), alpha=0.2),
            polygon3d([p00f, p01f, p11f, p10f], color=Color('darkgreen'), alpha=0.2),
            line3d([p00n,p00f], color=Color('grey')),
            line3d([p01n,p01f], color=Color('grey')),
            line3d([p11n,p11f], color=Color('grey')),
            line3d([p10n,p10f], color=Color('grey')) ];
eyept = point(EV,size=20,color='red');
axmag = 20.0;
WVg   = arrow(EV,(EV + axmag * WV), color=Color('blue'), width=16);
UVg   = arrow(EV,(EV + axmag * UV), color=Color('red'), width=16);
VVg   = arrow(EV,(EV + axmag * VV), color=Color('green'), width=16);
gobs = sum(frustum()) + sum(houseSidesWorld) + eyept + WVg + UVg + VVg;
gobs.show(aspect_ratio=(1,1,1));