Here are links for the lesson on hierarchical models.
Download the Walnum Chapter 8 Example Folder
This week, we will work through Chapter 7 of Foley and van Dam, and then return to OpenGL to see how the idea of hierarchical models might be realized. Please carefully read Chapter 7 now, then return to these notes.
We won't actually take up programming in PHIGS, or its simple cousin SPHIGS. But we will need to understand its essential issues.
Some common kinds of models include
This immediately raises the idea that the two instances of 'arm' should somehow be attached to the body in different places. We refer to the two arms that will appear in the final image, as 'instances' of the 'class' of arms. They will have some different data structures (because they must remember where they are) but also some common data structures (that represent their common shape.)
We can think of the 'arm' class as a subroutine, that is called twice during the execution of the upperBody subroutine of the robot's main program. This blurring between program and data is very common in graphics. Its advantages include an efficiency of data storage, since complex sub-parts are stored only once (imagine an auditorium full of chairs; you need only one chair model.)
Also, it facilitates animation, because we want to move the chairs around in the room - without necessarily recalculating tens of thousands of vertices and storing the new, slightly erroneous points. We just want to keep one official chair model, and work from it.
The FVD term for a system which manages geometric models stored in a data structure, is the "CSS" - the central structure storage. The CSS stores all the things listed in the above table. Contrast CSS with a display list, which usually just stores a list of the polygons (or other rendering primitives) that result from traversing a CSS model.
CSS can be used for pick correlation, and the returned object IDs would be meaningful to the modeling application. If you use a display list for pick correlation, you must be careful to retain enough object identity into the display list that the returned information is useful. ("polygon 15667? What's that part of?")
Consider the following polyhedron specification for a simple house (FVD p.297).
Polyhedron = {VertexList, FacetList}
VertexList = {V1, V2, V3, V4, V5, V6, V7, V8, V9, V10}
V1 = (x1, y1, z1), etc. // these would be real numbers
FacetList = {front = {1,2,3,4,5}, right = {2, 7 , 8, 3}, etc. }
The Polyhedron is a collection of facets that may or may not enclose a volume. Thus it could be a flat sheet, a piece of countryside, a bowl or a closed box like this house. The Facetlist is actually stored as a string of integers encoded as follows:
1,2,3,4,5,-1,2,7,8,3,-1, ...where the -1 values separate the facet descriptions. Similar means are used to store the vertex triples. The counterclockwise rule is followed for vertices as seen from the exterior of each polygon.
You construct the arrays needed to describe an object and then call
SPH_polyhedron (10,7, houseVertexList, house FacetDescriptions>Where 10 and 7 are the number of vertices and facets respectively. This stuff sounds a little like OpenGL, but the CSS is the difference. PHIGS is a retained mode package; it remembers everything in its CSS, whereas OpenGL just draws what you give it, and forgets it. If you want retention in OpenGL, you have to build it yourself.
One big advantage this gives you, is the ability to station multiple cameras in the same structural space. This is illustrated on FVD pages 300, 301.
SPH_openStructure(MOVED_HOUSE_STRUCT); SPH_setLocalTransformation(SPH_rotateX(33),REPLACE); SPH_setLocalTransformation(SPH_translate(10,10,0),PRECONCATENATE); SPH_polyhedron(...); SPH_closeStructure; SPH_postRoot(MOVED_HOUSE_STRUCT, whichView);Note that the availability of PRECONCATENATE means that they can have their rotate before their translate in the source code, and still get the desired effect. In OpenGL we have to reverse the order of these transformations because we only have postconcatenation.
Now we begin to use these ideas. In the first example, on FVD page 306, we see a "flat" program to construct a street scene. On page 307 we make a slightly more complex version by calling 'house' three times, but the resulting hierarchy is still flat. By 'flat', we mean that the code is just a series of model transformation settings and repeated calls to the procedure which draws the house. This would be exactly reflected in an OpenGL program, and takes no advantage of CSS. The resulting structure looks like
STREET Polyhedron "house" scale rotate translate Polyhedron "house" scale translate Polyhedron "house"The stored structure would have three copies of the geometry of the house in it. In the next layer of detail, we start using what's called a "structure execution element." The CSS now contains 3 pointers to the house's structure, instead of three copies of the house's geometry. Ultimately, we deepen the hierarchy by giving the house a chimney, so the "subroutine" for drawing the house now has a subroutine of its own. We wind up with a diagram that looks like figure 7.14, page 310.
How do you control these transformations? In SPHIGS, once you have drawn an object, to refer to the transformation directly requires that you use a SPH_label (p. 324.). You plant these labels during the construction, to mark the "articulation points' (e. g. the shoulders of the robot.) Then when you want the robot to swing his arms, you have to come back to these labelled points, delete one transformation element (p. 326) and add in a new angle of rotation.
A more elegant mechanism would be desirable. You would like to be able to label a particular transformation in such a way that you could just change its values "in place", without unlinking and relinking the structure twice. SPHIGS doesn't support this kind of light-weight structural editing; only the heavyweight process of chopping and pasting is provided.
If you don't store the transformation matrices in your own data structure, it's hard to see how you would have control over the 'cutting and pasting' operations. To imagine how we might do that, we will now go to Walnum's Chapter 8 and examine his animated robot code. The text example code is unfortunately quite hard to read, because of the constant "END CUSTOM CODE" an "START CUSTOM CODE". So I've stripped out the essentials in the following non-executable, annotated version. I have focused only on the robot, and so this example also omits things concerned with the electrons.
//***********************************
//animavw.h : Interface of the CAnimateView class
//***********************************
class CAnimateView: Public Cview
{
//MFC stuff, including
protected: CAnimateView();
DECLARE_DYNCREATE(CAnimateView)
public: CAnimateDoc* GetDocument();
//then
protected:
HGLRC m_hRC; // Rendering context
HPALETTE m_hPalette;
GLfloat m_angle, m_angle2; // Robot's armjoint & body twists
GLfloat m_speed; // How fast he flaps his arms
//MFC stuff, then
void DrawWithOpenGL(); // Gonna be our OnDraw
void SetupLogicalPalette();
void AnimateRobot();
//***********************************
//animavw.cpp: Implementation of the CAnimateView class
// We begin with the obligatory constructor and destructor
//***********************************
CAnimateView::CAnimateView() // The constructor
{
m_hPalette = 0;
m_angle = 0.0f;
m_angle2 = 0.0f;
m_speed = 5.0f;
m_armsGoingUp = TRUE;
}
CAnimateView::~CAnimateView()
{ // Nothing special to do upon destruction
}
//----------------------------------------------------
//Now we provide our own OnDraw
//----------------------------------------------------
void CAnimateView::OnDraw(CDC* pDC)
{
CAnimateDoc* pDoc = GetDocument();
ASSERT_VALID(pDOC);
if (m_hPalette)
{
SelectPalette(pDC->mhDC, mhPalette,FALSE);
RealizePalette(pDC->m_hDC);
}
wglMakeCurrent(pDC->m_hDC, m_hRC); // turn on render context
DrawWithOpenGL();
SwapBuffers(pDC->m_hDC);
wglMakeCurrent(pDC->mhDC, NULL); // turn off render context
//----------------------------------------------------
//We skip the code of these routines
//----------------------------------------------------
CAnimateView: OnCreate // creates the PixelFormatDescriptor
CAnimateView: OnDestroy // kills timer, deletes render context & palette
//----------------------------------------------------
//OnSize: What to do when the window changes size or is otherwise tickled.
//----------------------------------------------------
void CAnimateView: OnSize(UINT nType, int cx, int cy)
{
Cview::Onsize(nType, cx, cy); // Asks Cview to run ITs onsize first.
CClient DC clientDC(this); // Dam' fi know... WFC magic.
wglMakeCurrent(clientDC.m_hDC, m_hRC);
// Now we should understand what is happening from here down.
// Namely, rather standard setups of the viewing parameters.
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
glFrustum (-1.0, 1.0, -1.0, 1.0, 2.0, 9.0);
glViewport (0, 0, cx, cy);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
GLfloat Light0Ambient[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat Light0Diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat Light0Position[] = {0.0f, 0.0f, 0.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, light0Position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glTranslatef(0.0f, 0.0f, -6.0f);
wglMakeCurrent(NULL, NULL);
}
//----------------------------------------------------
//OnTimer: What to do when the clock ticks.
//----------------------------------------------------
void CAnimateView::OnTimer(UINT uIDEvent)
{
//As usual, I've omitted the atom code and show you only the robot code
m_angle2 += m_speed / 2; // Rotate the robot around its vert axis
if (m_angle2>355)
m_angle2 = 0.0f;
if (m_armsGoingUp)
{
m_angle += m_speed; // Flap its arms
if (m_angle > 90.0f)
m_armsGoingUp = FALSE;
}
else
{
m_angle -= m_speed;
if (m_angle < 0.0f)
m_armsGoingUp = TRUE;
}
Invalidate (FALSE); // We ought to go study timers to
Cview::OnTimer(nIDEvent); // understand this stuff... later.
}
//----------------------------------------------------
//We skip the code of thisroutine
//----------------------------------------------------
CAnimateView::PreCreatWindow(CREATESTRUCT& cs)
//----------------------------------------------------
//Stuff for the robot; sorta magic.
//----------------------------------------------------
CAnimateView::OnObjectRobot()
{
m_object=Robot;
m_angle = 0.0f;
m_angle2 = 0.0f;
}
CAnimateView::OnUpdateObjectRobot(CCmdUI* pCmdUI);
{
if (M-object == Robot)
pCmdUI -> SetCheck(TRUE)
else
pCmdUI -> SetCheck(FALSE);
}
//----------------------------------------------------
//----------------------------------------------------
//Now for the main event:
//----------------------------------------------------
//----------------------------------------------------
void CAnimateView::DrawWithOpenGL()
{
glShadeModel(GL_SMOOTH); // Mostly for benefit of atoms
glEnable(GL_DEPTH_TEST);
glclearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
AnimateRobot();
glFlush();
//----------------------------------------------------
// And HOW, pray tell, are we to animate the robot?
//----------------------------------------------------
void CAnimateView::AnimateRobot()
{
GLfloat materialAmbDiffGreen[] = (0.2f, 0.8f, 0.5f, 1.0f};
//and also some red, gray and blue materials are defined.
glMaterialfv(GL-FRONT, GL_AMBIENT_AND_DIFFUSE,
materialAmbDiffGreen); // First part of robot is to be green
glPushMatrix(); // Save this pose; we're coming back to it.
// Draw the torso
glTranslatef(0.0f, 0.0f, -1.0f);
glRotatef(m_angle2, 0.0f, 1.0f, 0.0f); // Rotate on vert axis
auxSolidCube(1.0f);
// Draw the abdomen
glTranslate(0.0f, -1.1f, 0.0f);
auxSolidCube(0.9f);
// Draw the head
glTranslate(0.0f, 2.1f, 0.0f);
auxSolidCube(0.75f);
// Draw eyes and a nose. Skip the details. Robot is facing along
// the +x axis.
glTranslate(0.60f, -1.2f, -0.2f); // Get in position for arms.
glPushMatrix(); // Save this for coming back to do second arm.
// Now we do the left arm.
glTranslafef(0.0f, 0.0f, 0.5f); // Push out in z direction
glRotatef(m_angle, 1.0f, 0.0f, 0.0f); // rotate around x.
// A glMaterialfv call changes the color of the arms to red.
auxSolidCylinder(0.15f, 0.8f); // cylinder's diam, length
glTranslatef(0.0f, 1.0f, 0.0f); // now add on y-translate
glRotate(-m_angle/2.0f, 1.0f, 0.0f, 0.0f); // counter-rotate next seg.
auxSolidCylinder(0.15, 0.8f);
glTranslate(0.0f, 1.0f, 0.0f); // For the third arm seg,
glRotatef(-m_angle/2.0f, 2.0f, 0.0f, 0.0f);// add a bit more y.
auxSolidCylinder(0.15, 0.8f);
glPopMatrix(); // back to the center
// now do the left arm, just like the right arm.
glPopMatrix(); // Ready for next animation step.
}
We see the quite similar succession of translations and rotations and calls to a routine to draw the object-part (auxSolidCube or auxSolidCylinder), as we did in SPHIGS. However, there is in this case no stored data. It's just drawn whenever the call occurs.
Now we must move on to make up a midterm exam question about hierarchical models.