Camera Placement: Viewing a House Part 2

This notebook shares much in common with the Part 1 notebook that shows the 3D viewing volume. Also known as the frustum.

This notebook differs from the previous in that it actually shows how to enumerate rays eminating from pixels on the image plane and moving out into the scene.

Ross Beveridge September 13, 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 [33]:
%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 [34]:
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 [35]:
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 [36]:
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 [37]:
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),
   ];

Specifying a Camera

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

In [38]:
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    = -75;      # The near and far clipping planes 
left  = -20; right  =  20; 
top   =  20; bottom = -20; 
width =   8; height = 8; 
# 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);

Next we have some support python code to assist with the visualization. On first pass through this notebook you may skip over this code since understanding it is not critical to the overall point of the Notebook.

In [41]:
from sage.plot.colors import rainbow;
pixcmap = rainbow((width * height) + width);
def pixcolor(i,j):
    return pixcmap[i+(j*height)];
def pixelPt(i,j):
    px = i/(width-1)*(right-left)+left;
    # py = j/(height-1)*(top-bottom)+bottom;
    py = j/(height-1)*(bottom-top)+top;
    pixpt = EV + (near * WV) + (px * UV) + (py * VV);
    return point(pixpt, size=10, color=pixcolor(i,j));
def pixelRay(i,j):
    px = i/(width-1)*(right-left)+left;
    # py = j/(height-1)*(top-bottom)+bottom;
    py = j/(height-1)*(bottom-top)+top;
    pixpt = EV + (near * WV) + (px * UV) + (py * VV);
    shoot = pixpt - EV; shoot = shoot / shoot.norm();
    raypt = pixpt + shoot * abs(far-near);
    return arrow3d(pixpt, raypt, width=16, color=pixcolor(i,j));
drawPts  = [pixelPt(i,j) for i in range(width) for j in range(height)];
drawRays = [pixelRay(i,j) for i in range(width) for j in range(height)];
eyept    = point(EV,size=20,color='red');
ptrays   = drawPts + drawRays + [eyept];

Visualizing Rays

Now the 3D visualization is important. In this you can see the major key components of the camera relative to the scene.

In [42]:
show(sum(houseSidesWorld+ptrays),aspect_ratio=(1,1,1));