Tutorial Two - Campbell's Soup Can

 Download the WocCampbellsSoupCan EXE (152KB) in compressed ZIP format.

 Download the WocCampbellsSoupCan project files (79KB) in compressed ZIP format.

For this and all the tutorials you will need to download the WOC header and implementation files. I recommend that you unzip them into a folder named woc and locate it at the same level as (i.e. a sibling of) the project folders which use it. This is because the projects look for the WOC files at the path: ..\woc\ as we'll see later.

 Download the end-of-can image (25KB) in compressed ZIP format.
 Download the soup can label image (47KB) in compressed ZIP format.
   
About the tutorial
This sample is a quick introduction to the higher-level services of WOC. Rather than definining our own geometry, we'll be making use of WOC's model loaders so we won't need to know too much about WOC's class hierarchy. You will learn how to define your own 3-D model, how to substitute your model for the view's default one, how to use some of the built-in model processing features and finally how to set the initial modelview transformations of your scene.

WinZip® brings the convenience of Windows to the use of Zip files and other compression formats. If you don't have "the most celebrated shareware app in the history of computing" already, use the above link to download it.
   
The steps
1. You first need to set up a new project which should start life in the same state as the WocSkeleton project from the previous tutorial. You can either follow the previous tutorial again from scratch or, preferably, you can do some copying and renaming. I'll go through this second option now.

First, copy the WocSkeleton project folder and rename it WocCampbellsSoupCan. Next, all files beginning WocSkeleton must be renamed to WocCampbellsSoupCan, leaving the file extension alone. Now we need to search for the literal 'WocSkeleton' in a few files and replace every occurrence with 'WocCampbellsSoupCan' - a task you can use Notepad's Edit/Replace... command to do, or better yet an advanced search and replace utility if you have one. The files in which to search and replace are: WocCampbellsSoupCan.cpp, WocCampbellsSoupCan.dsp, WocCampbellsSoupCan.dsw, WocCampbellsSoupCan.rc and resource.h.

2. You now have a renamed copy of the original skeleton project, so open it in Visual Studio and build and run it to check it's okay. From here on in, if I make reference to any WOC classes or concepts that are not either intuitive or clear from the context then please refer to the WOC Class Reference. There you'll find Class Diagrams illustrating the associations between classes, along with full explanations of each class and its members.

Choose File/New... and add to the project a new C/C++ Header File named InitialiseOGLWndToCampbellsSoupCan.h. Paste the following function skeleton into it:
void InitialiseOGLWndToCampbellsSoupCan(CWocOGLWnd* pOGLWnd)
{
}
In the file WocCampbellsSoupCan.cpp, after the inclusion of resource.h, include the header file we have just created:
#include "InitialiseOGLWndToCampbellsSoupCan.h"
Now, in the same file, immediately before the final return statement of WinMain, paste the following call to our new function:
    InitialiseOGLWndToCampbellsSoupCan(theApp.GetMainView());
The function we defined earlier requires a pointer to a non-const CWocOGLWnd object so here we are calling on the global CWocApp object to return a pointer to its main view (which is the view belonging to the application's main frame window).

3. Returning to the InitialiseOGLWndToCampbellsSoupCan function, let's add some code. First, we'll clear out the default model which the WOC framework placed into our OGLWnd on startup.
    pOGLWnd->ClearModels();
If you compile and run now, you'll see that the default model has gone. Let's put something in its place.
    CWocModel* pModel = pOGLWnd->AddModel();
    CWocGroup* pGCan = pModel->AddGroup();
    CWocModelLoaderOpenCylinder cylLoader;
    cylLoader.Load(pModel, pGCan, 40, TRUE);

    // Finishing touches.
    pModel->GenerateFaceNormals();
    pModel->AdjustOGLState(pOGLWnd);
The first line adds a new, initialised, Model to the OGLWnd and gets back a pointer to it. The next line adds a new, initialised, Group to the new Model and gets back a pointer to it. Next, we declare a model loader instance, this one specialising in loading the geometry of an open cylinder, or pipe, whose length and diameter are both one unit. The loader is asked to load and it is supplied with: the Model and Group to load into, the number of divisions to break the surface of the object into (what I call the geometry's granularity), and a TRUE boolean argument indicating that we require the loader to generate texture co-ordinates as well as points. The model is now requested to compute face normals for itself and then is asked to make appropriate settings to OpenGL's state for its own requirements. As the default model will already have activated lighting, this line is not strictly necessary here, but it is a good habit to get into calling the function as the very last thing you do in the definition of a model. Compile and run now and you'll see a rotating pipe which is open at the ends. By default, only one side of a polygon is rendered so when you see the pipe end-on it you will be looking inside it at the back of the polygons and so nothing will be drawn for a moment until it rotates some more. Activate the mouse manipulation mode (Ctrl + right-mouse click) if you want to get a better view of the pipe, or even stop auto-rotation via the OGLWnd Properties Dialog (double right-mouse click). Notice that the surface of the pipe is not curved but faceted because of the type of normals generated. To remedy this, change the function call above to GenerateVertexNormals, recompile and re-run and notice the difference.

4. Texturing next. If you are following along step-by-step then right now you'll need a couple of image files containing the can's end image and label. You will need the end-of-can image (25KB) and the soup can label image (47KB), both in compressed ZIP format. Unzip the bitmaps and place them in your project folder now. Then, on the ResourceView tab, import the bitmaps (the .BMP files) and dismiss the informational messagebox each time. It is worthwhile pointing out at this point that you must always use 24-bit BMP files with WOC, whether you incorporate them into your resources or read them directly from disk. WOC requires a byte per RBG component per pixel. Also, the OpenGL documentation requires that the dimensions of texture files be a power of two. However, you may be able to get away with simply making them even: you'll have to experiment. If whilst using your own texture files with WOC you have problems, these are the two points to check first - color depth and dimensions.

Your bitmap resources will be called IDB_BITMAP1, or something similar. Rename them now to IDB_END and IDB_LABEL for the can-end and the label respectively and then paste the following line immediately before the 'finishing touches' comment:
    pGCan->Material()->SetTexture(CWocTexture(pOGLWnd, IDB_LABEL));
The Material member of a Group returns a pointer to its non-const Material. You may know from the WOC Class Reference that any number of Groups may be referencing the same Material. Therefore any changes you make to the Material will by association affect the referencing Groups. Here we are calling SetTexture on the Material pointer and supplying a temporary CWocTexture object. The Texture's constructor requires a pointer to the OGLWnd because it will be calling OpenGL functions which require an OpenGL rendering context to be current. OGLWnd objects encapsulate a rendering context as well as code to bring it in and out of currency. The Texture also needs to know where to get its image data from. In this case were are using resources but we could easily well have supplied the path to a file on disk. If you compile and run, you will see that the pipe now has its label on and looks more like a soup can.

3. Now to close the soup can by giving it both top and a base. To do this we will create two new Groups which share the same Geometry. We will not define a Material for them, so they will default to a grey Material the definition of which is supplied by the static member CWocMaterial::SpecifyDefaultGLMaterial. In addition, we will define different transformations for the two Groups so that they end up at either end of the body of the can. Paste the following lines immediately before the 'finishing touches' comment:
    CWocGroup* pGTop = pModel->AddGroup();
    CWocModelLoaderDisk diskLoader;
    diskLoader.Load(pModel, pGTop, 40, TRUE);
    pGTop->Material()->SetTexture(CWocTexture(pOGLWnd, IDB_END));
    pGTop->Transformations()->AddNew(CWocTranslation(0.5f, 0, 0));
    pGTop->Transformations()->AddNew(CWocOrthogonalRotation(90, Y_AXIS));
    CWocGroup* pGBottom = pModel->AddGroup(pGTop->Material(), pGTop->Geometry());
    pGBottom->Transformations()->AddNew(CWocTranslation(-0.5f, 0, 0));
    pGBottom->Transformations()->AddNew(CWocOrthogonalRotation(-90, Y_AXIS));
Note the different model loader being used here - one which specialises in generating the geometry (and optionally texture co-ordinates) of a disk with diameter one unit. The only unfamiliar parts here are the invocations of the Transformations member of the new Groups. This member returns a pointer to its non-const Transformations instance and here we are using it to add new Transformation-derived objects. In either case a CWocTransformation is first added whose construction arguments represent the distances to translate in the x, y and z directions respectively. The arguments used here push the disks plus or minus half a unit along the x-axis (remember, the open cylinder's length is one unit). The disks are now in position at the ends of the can, but they lie on the z=0 plane at right-angles to their desired orientation. In order to turn them onto the x=0.5 and x=-0.5 planes the second transformation added to each Group is a CWocOrthogonalRotation which rotates them 90 degrees and -90 degrees respectively about the y-axis. Rotations are counter-clockwise about the chosen axis when looking towards the origin along that axis. OpenGL uses a right-hand co-ordinate system so you can use your right hand as a mnemonic. Point your thumb in the direction of a ray leaving the origin along the axis in question and curl your fingers around the imagined ray. Looking at the tip of your thumb, your fingers will curl counter-clockwise. This same right-hand mnemonic can be used to remind you that in an untransformed right-hand co-ordinate system the eye looks down the positive z-axis toward the origin and that the normal vector of a triangle points towards the eye if the triangle's vertices are drawn in counter-clockwise order. For those interested, see the comment at the top of woc.h with more details on the subject.

One last, important thing to note about the above code fragment is the second call to AddGroup. This time we are supplying pointers to resources to be re-used by the Group being created, specifically the Material and the Geometry of the top-end of the can.

4. Not much left to do to finish up. We need to stand the can up on its base and also give it nicer proportions by making it a bit taller. Here's how (before 'finishing touches'):
    pModel->Transformations()->AddNew(CWocOrthogonalRotation(90, Z_AXIS));
    pModel->Transformations()->AddNew(CWocScaling(1.2f, 1, 1));
The thing to note here is that we have rotated the Model's local co-ordinate system and so the x-axis is now pointing upwards. Therefore to make the can taller we scale along the x-axis and not along the y-axis. Also, we are scaling after translating in agreement with the way OpenGL composes transformation matrices together: to nonuniformly scale a local co-ordinate system and then rotate it would result in a shear (in the same way as a rotation followed by a nonuniform scaling would in a grand, fixed co-ordinate system). Note that the above transformations are defined on the entire Model's Transformations so they will affect all of the Model's Groups. No matter how the Groups of a Model are transformed, you can consider the entire Model as one fixed unit when defining its own Transformations. Similarly, as we'll do next, when defining the OGLWnd's Transformations, you can consider all the Models which go to make up a scene as one fixed unit. The entire scene in view in the OGLWnd will next be rotated slightly to move the can's base closer to the eye. It is also too close to the eye so it needs to be moved along the negative z-axis. Paste the following code, this time right at the end of the function just inside the closing braces:
    pOGLWnd->TransformationsPreservingAutoRotate()->OrthogonalRotate(-14);
    pOGLWnd->TransformationsPreservingAutoRotate()->Translate(0, 0, -0.35f);
If we had used the OGLWnd's Transformations member function then some auto-rotation functionality would have been switched off because the WOC framework has to make certain assumptions about what is in the OGLWnd's Transformations instance in order to safely expose the adjustment of auto-rotation parameters to the programmer and the OGLWnd Properties Dialog. Implicit in a call to Transformations is the license to do whatever the caller wishes to the returned Transformations instance. Therefore the framework has to protect itself by being cautious and disabling access to auto-rotation properties. As long as we're not adding, removing nor renaming a transformation in the OGLWnd's Transformations then the protective precaution is not necessary. So, in cases when the user is certain that auto-rotation will not be adversely affected, it is safe to use the alternative TransformationsPreservingAutoRotate member function which returns the same pointer as does Transformations but it retains the integrity of auto-rotation. If you want to test this, switch the above calls for calls to the paranoid Transformations, compile and run and examine the Properties Dialog. You will notice that the auto-rotate controls are disabled. Remember to change the code back to the above version and now you'll see the auto-rotate controls are enabled. Finally, paste the following code at the end of the function, just inside the closing braces:
    pOGLWnd->SetAutoRotateDegrees(-0.2f);
    pOGLWnd->Light()->Transformations()->SetTranslation(1,2,2);
This simply slows down the auto-rotation and moves the default light (GL_LIGHT0) to a slightly more interesting position. Compile and run now, and there's the finished demo.

A quick postscript about the texture used on the ends of the can. I couldn't find a suitable texture on the WWW so I began thinking about having a digital photo of a can-end taken. As it happened, though, I quickly wrote a new class CWocModelLoaderRippleGrid (by copying most of CWocModelLoaderGrid) which puts ripples into the normally plane grid. I temporarily altered the new class to leave the centre of the grid flat in order to emulate the end of a can and then rendered a model loaded by the new loader. It was a screenshot of the resulting lit model that ended up as the texture image. This was a very good illustration of leveraging WOC to come up with a quick and easy solution.
 
last updated: 4-oct-01