Interactive Computer Graphics I

CAP5725 - Fall 1996

Computer Science Department

University of Central Florida

J. Michael Moshell, Professor - moshell@cs.ucf.edu

WEEK 6: Hierarchical Objects

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.

Why the Robots Everywhere?

Well, we have an intuitive belief that robots are made of rigid parts with rotary joints, so they are easy to model. The robot in FVD Chapter 7 is more sophisticated than the one in the Walnum book's chapter 8, because it has a true hierarchical model, expressed in the PHIGS system. PHIGS = "Programmer's Hierarchical Interactive Graphics System", which was one of the many attempts to produce standards for computer graphics back in the 70's and 80's.

We won't actually take up programming in PHIGS, or its simple cousin SPHIGS. But we will need to understand its essential issues.

What is a Model?

A model is a representation of some (but not all) of the features of a concrete or abstract entity. Models help us visualize and understand the structure and behavior of an entity. They let us experiment with inputs and see the outputs.

Some common kinds of models include

  1. Organizational models
  2. Quantitative models
  3. Geometric models
We can use geometric structures to model organizations (e. g. an "organization chart") and quantitative models (e. g. graphs.) However the most common use of geometrical models is to represent real world objects.

Hierarchy in Geometric Models

We often use building blocks to represent complex objects. In the robot example of the FVD text (p. 289), we see two possible representations. A tree is actually the simpler of the two, because it shows separate data structures for the two arms and two thumbs. A Directed Acyclic Graph (DAG) uses the object oriented concept of a class of arms and another class of thumbs. Thus, there are two links from "upper body" to the arm representation, and one from arm to thumb.

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 Readers and Writers of an Application Model

The model is acted upon by at least three classes of software events:
  1. Software that builds, modifies and manipulates the model
  2. Software that traverses the model for analysis
  3. Software that traverses the model for display
Example of the first: your CAD program for Lab 2.
Example of the second: a mechanical-engineering stress analysis program
Example of the third: also in your CAD program, you will need a redraw capability.

PHIGS' Basic Functionalities

PHIGS supports 'structures', which we would think of as graphical objects. A structure is represented in a data file, which can be loaded into RAM just like a word processor document or other persistent object. The basic things you can do to a structure are
  1. Opening and closing
  2. Deleting
  3. Inserting or deleting structure elements:
    1. primitives
    2. attributes
      1. modeling transformations
      2. substructure invocations
      3. simple inheritable attributes
        1. color, texture, transparency, material properties
  4. Posting for display
This last item - posting for display - is the traversal. We have seen in our OpenGL that we set up a viewing matrix, then execute some routine to draw our object. If we had a collection of objects rather than just one, we'd need to have a way of managing all of them when it came time to view the scene.

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?")

SPHIGS' Geometry.

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.

Modeling Transformations

In OpenGL, when you invoke a modeling transformation, the resulting matrix is automatically post-multiplied ("post-concatenated") with what's already on the stack. In PHIGS, the modeling transformation subroutine just returns the matrix to you. You use SPH_setLocalTranformation to then either replace, pre-concatenate or post-concatenate this onto the top element of the stack. Here's an example.
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.

Varieties of Hierarchy

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.

The Robot's Hierarchy

Now consider figure 7.15, page 312. This one is quite similar to the house/chimney model. Walk through the issue of how this repetitive pattern, while storage-efficient, produces two thumbs which cannot move independently. (The ARMS can move independently, because they have different instance transformations back in the Upper Body data structure. But the thumbs don't have distinct transformations, and so have no means of independent movement.

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.

Back to OpenGL

What are the implications of all this for our projects? Well, to execute Lab 2, you don't really have to build a hierarchical system. You can just provide every object with its own rotation, scaling and translation attributes. However, when you try to build almost any part of Lab 3, you are going to need to be able to connect objects together and move the aggregated object. For this, SPHIGS provides an explicit model.

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.
}

Contrasting OpenGL and PHIGS

It is immediately apparent that, at least in the style of the Walnum book, there is really no model data structure except in the aggregated variables named m_angle, m_angle2 and m_speed. (We skipped the code for changing m_speed, via menu items.)

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.

The crucial difference

is... that with a stored structure, you can write code to edit and modify your object's component parts and their relatonships. In the static structure of the OpenGL example, you cannot insert a new arm, or do anything else that wasn't programmed beforehand into your demo.

Now we must move on to make up a midterm exam question about hierarchical models.