180 likes | 201 Views
Learn how to represent and manipulate drawable objects independently of their types, discuss OO solutions in C++, handle hierarchical objects conveniently. Understand polymorphism, inheritance, and container usage. Useful for computer science students.
E N D
More on Drawable Objects,Hierarchical Objects Glenn G. ChappellCHAPPELLG@member.ams.org U. of Alaska Fairbanks CS 481/681 Lecture Notes Wednesday, January 21, 2004
Review:Drawable Objects [1/4] • Now we begin looking at how to represent a scene internally. • It will be convenient to be able to deal with drawable objects independent of their characteristics. • There are many ways to do this; we discuss the usual OO solution, as implemented in C++. • It is reasonable to represent different kinds of drawable objects with different data types. • Deriving all of these from a common base class (“Drawable”?) allows us to draw them without knowing what type they are. CS 481/681
Review:Drawable Objects [2/4] • Here is one way to write our base class: // class Drawable // Abstract base class for drawable objects class Drawable { public: virtual ~Drawable() {} virtual void draw() const = 0; }; • That’s all! • The destructor is a C++ detail: a base class should have a virtual destructor. • Note that Drawable is an abstract class (due to the “= 0”); we cannot declare objects of type Drawable. CS 481/681
Review:Drawable Objects [3/4] • To declare a drawable object type, do something like this: class Cokebottle : public Drawable { public: virtual ~Cokebottle() {} virtual void draw() const; Cokebottle():Drawable(),iscokeit(false) {} void cokeisit() { iscokeit = true; } private: bool iscokeit; // true if Coke is it. }; void Cokebottle::draw() const { Draw a cokebottle here. } CS 481/681
Review:Drawable Objects [4/4] • To use objects polymorphically, refer to them via base-class pointers or references: void draw_this(const Drawable & obj) { obj.draw(); // Calls the proper virtual function. } • Function draw_this can take a parameter of any type derived from Drawable. • The above code would not work correctly if the object were passed by value. • Now we can do this: Cokebottle c; draw_this(c); CS 481/681
More on Drawable Objects:Inheritance and Containers [1/3] • Since we can deal with all drawable objects the same way, we can stick all of our objects into a container (array, vector, etc.), and iterate through that container to draw the scene. • However, the following will get us into trouble. std::vector<Drawable> scene; • Why? • Hint: There are two big problems here. CS 481/681
More on Drawable Objects:Inheritance and Containers [2/3] std::vector<Drawable> scene; • First, this will not compile. • Since Drawable is an abstract class, we cannot create objects of type Drawable. • But, second, even if we make Drawable a concrete class, this is a problem. • A Cokebottle is probably bigger than a Drawable. So we cannot store a Cokebottle in the space meant for a Drawable. • The problem resulting from trying to store an object of a derived class in a base-class variable is called slicing. CS 481/681
More on Drawable Objects:Inheritance and Containers [3/3] • Solution: Use base-class pointers. • Be sure that objects are delete’d properly! std::vector<Drawable *> scene; • To add to the scene: scene.push_back(new Cokebottle); • To draw the entire scene: std::vector<Drawable *>::const_iterator it; for (it = scene.begin(); it != scene.end(); ++it) (*it)->draw(); CS 481/681
Hierarchical Objects:Overview • Suppose we wish to draw a moving object with moving parts. • This is called a hierarchical object. • See face.cpp for an example. • Two questions: • How can we handle this conveniently with our graphics API? • What sorts of data structures are appropriate for storing such an object? • We begin with the first question. CS 481/681
Hierarchical Objects:Introduction • We think of an object with moving parts as a hierarchy. • At the top of the hierarchy is the object as a whole. • At the next lower level in the hierarchy are the moving parts (for example, the eyes in face.cpp). • Moving parts can contain moving parts; these are at an even lower level in the hierarchy. • We use stack operations to handle the transformations involved in drawing hierarchical objects. CS 481/681
Hierarchical Objects:Transformations [1/4] • We want a moving object to have a moving part. • We should be able to move the object as a whole. • We should also be able to move the part as a part of the object. • Thus, one transformation is applied to the object as a whole, but two transformations are applied to the moving part. • The transformation for the moving part needs to be done before the transformation of the object as a whole (right?); therefore it comes later in the code (right?). CS 481/681
Hierarchical Objects:Transformations [2/4] • Pseudocode: glPushMatrix(); Set up transformation for whole object Draw non-moving parts glPushMatrix(); Multiply current matrix by transformation for moving part Draw moving part glPopMatrix(); glPopMatrix(); • This is the general form of the code to draw a hierarchical object. • What are the push/pop really good for? See the next slide … CS 481/681
Hierarchical Objects:Transformations [3/4] • What if a hierarchical object has more than one moving part? glPushMatrix(); Set up transformation for whole object Draw non-moving parts glPushMatrix(); Multiply current matrix by transformation for moving part 1 Draw moving part 1 glPopMatrix(); glPushMatrix(); Multiply current matrix by transformation for moving part 2 Draw moving part 2 glPopMatrix(); glPopMatrix(); • It is convenient to make some of the pieces above into separate functions. • Then follow this rule: If a function changes a matrix, then it also restores it to its prior value. • This is essentially the form of our example (discussed shortly). CS 481/681
Hierarchical Objects:Transformations [4/4] • What if a hierarchical object has more than two levels? glPushMatrix(); Set up transformation for whole object Draw non-moving parts glPushMatrix(); Multiply current matrix by transformation for moving part 1 Draw moving part 1 glPushMatrix(); Multiply current matrix by transformation for sub-part 1 of moving part 1 Draw sub-part 1 of moving part 1 Etc … CS 481/681
Hierarchical Objects:Example (face.cpp) [1/4] void display() { glClear(GL_COLOR_BUFFER_BIT); // Draw face glPushMatrix(); glTranslated(face_move, 0.0, 0.0); glRotated(face_angle, 0,0,1); glScaled(face_scale, face_scale, face_scale); draw_face(); glPopMatrix(); • This is the beginning of the display routine. It sets up the transformation for the face, then calls draw_face to do the drawing. CS 481/681
Hierarchical Objects:Example (face.cpp) [2/4] • Here is a portion of the code for function draw_face: // Draw head glColor3d(0.8, 0.6, 0.4); glCallList(disk_list); // Draw left eye (on viewer's right) glPushMatrix(); glTranslated( 0.4, 0.3, 0.0); glScaled(0.2, 0.2, 1.0); draw_eye(); glPopMatrix(); // Draw right eye (on viewer's left) glPushMatrix(); glTranslated(-0.4, 0.3, 0.0); glScaled(0.2, 0.2, 1.0); draw_eye(); glPopMatrix(); • Because we handle transformations properly, we can use the same function to draw both eyes. CS 481/681
Hierarchical Objects:Example (face.cpp) [3/4] • Each function can be written to draw its part within a square of side 2, centered at the origin (x & y go from –1 to 1). • Then we set up the transformation to put the part in the proper place before we call the function that draws it. • The function that does the drawing uses the transformation it is given, modifies it if necessary, but always restores it to the original value. void draw_eye() { // Draw white of eye glPushMatrix(); glScaled(1.0, 0.4, 1.0); glColor3d(1.0, 1.0, 1.0); glCallList(disk_list); glPopMatrix(); CS 481/681
Hierarchical Objects:Example (face.cpp) [4/4] • Comments • Each function is written to draw the appropriate part (and all sub-parts) in some kind of “standard position”. • Again, sub-part transformations come before the main transformations, which means they are later in the code, which means they can go in the function that draws the sub-part (right?). • This is all very convenient and easy to use, once you wrap your mind around it. • We are very clear about expectations for matrix mode, etc., when entering and leaving each function. • The initial set-up is done when the reshape function is first called. • After that, whenever a function finishes, we are careful to leave things the way they were when it started. CS 481/681