Game Dev Diary 5: About Textures and 2D


After we finished the control schemas last time, it’s time to look a bit deeper into OpenGL.

Since we want to be able to build a basic UI, we will take a look at drawing 2D in OpenGL today. Also we will have a look at texturing our scene and UI elements.

Using 2D Projection

OpenGl is a great tool, to present 3d worlds, as we know. But sometimes we want to simply present 2D content to the user, without worrying, where the camera is or how our scene is lit at a given moment.

We can enable OpenGL to draw 2D content directly to the screen, by using an orthographic projection mode.

Since we want to be able to do that for a specific portion of our drawing cycle, regardless of the specifics of the default projection mode and need to reset it to the default after we’re done, we can use OpenGL’s feature to push and pop matrices from an OpenGL defined stack.

Have a look at the code below, which should go into your OpenGLView’s drawing function, after the drawing of the 3D Objects and right before the call to glFlush():

    /*
     * set orthographic projection
     */
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    long width = [self bounds].size.width;
    long height = [self bounds].size.height;
    glOrtho(0, width, 0, height, 0, 1);
        
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glDisable(GL_DEPTH_TEST);
    
    // draw 2D Stuff here...

    /*
     * reset the projection mode to whatever was before
     */
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glEnable(GL_DEPTH_TEST);

First we set the matrix mode to GL_PROJECTION since that is the matrix we want to manipulate here.

Then we call glPushMatrix(), which stores the current state of the matrix to a stack and creates a copy as the active matrix, that we can manipulate safely.

The function call to glOrtho() sets OpenGL into an orthographic projection mode. Since we used the actual size of our view, to specify the bounds of the orthographic viewport, the coordinates used by OpenGL will match the pixel height and width of our view perfectly.

Then we switch back to the MODELVIEW matrix, to prepare for drawing, whatever we want to draw. We also disable depth testing, since we don’t need it in 2D (since we don’t have any depth) and there may even be cases, in which depth testing will make for wired results.

After drawing our 2D content, we need to revert all the OpenGL states to the default, which means, pop-ing the MODELVIEW matrix, as well as the PROJECTION matrix back to normal, setting the active matrix to be the MODELVIEW matrix again and re-enableling the depth testing.

Drawing to the 2D Pane

Let’s start using the 2D Context that we just created, to actually do something.

Here’s how you draw a little white square to the lower left of the view, that we can later add a texture to.

    glBegin(GL_QUADS);
    glColor3f(1, 1, 1);
    glVertex2f(50, 150);
    glVertex2f(150, 150);
    glVertex2f(150, 50);
    glVertex2f(50, 50);
    glEnd();

By now, you should be pretty familiar with the code above.

The only new thing to note here is that we use glVertex2f() to create the vertices. That function simply omits the Z-coordinate, since we don’t need it in the 2D scope. We could also have created the vertices, using the glVertex3f() function and setting the third parameter to zero, which would have had the same effect.

White is Boring. Let’s add some Texture

When we run the scene now, we notice a little white rectangle. Let’s make things a bit more interesting and add some texture to it.

As with many things, when OpenGL creates a texture, it let’s you keep a GLuint value as an id of the created texture. That’s why we first need to declare a field variable, to hold the texture id, we want to apply to the rectangle.

In the header file of your OpenGL view, add the following:

@interface MyOpenGLView : NSOpenGLView {
    long lastTicks;
    ALuint satelitteALSourceId;
    NSObject<MyGameController>* controller;
    float cameraLat, cameraLon, cameraDist;
    GLuint aRavenTex;
}

To create a texture from an image in our application bundle, we can use the NSImage class and use it’s bitmap representation, to transfer the image data to OpenGL.

At the end of your prepareOpenGL method, add:

    NSImage* img = [NSImage imageNamed:@"raven.png"];
    NSBitmapImageRep* bmp = [[NSBitmapImageRep alloc] initWithData:
                             [img TIFFRepresentation]];

    glGenTextures(1, &aRavenTex);
    glBindTexture(GL_TEXTURE_2D, aRavenTex);
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 
                 [img size].width, [img size].height, 0, 
                 GL_RGBA, GL_UNSIGNED_BYTE, 
                 [bmp bitmapData]);

First we load the image by name. Cocoa will automatically look in our application bundle for an image with that filename and load it.

Since we need an uncompressed bitmap, to give to OpenGL, we than create a TIFF representation of the image and load it as a bitmap.

Next, we create the OpenGL texture, bind it so that we can manipulate it, set some basic parameters and finally load our image data to the GPU.

Now our texture is ready to use. Let’s use it. Change the code that creates our rectangle, so that it looks like this:

    glBindTexture(GL_TEXTURE_2D, aRavenTex);
    glBegin(GL_QUADS);
    glColor3f(1, 1, 1);
    glTexCoord2f(0, 0);
    glVertex2f(50, 150);
    glTexCoord2f(1, 0);
    glVertex2f(150, 150);
    glTexCoord2f(1, 1);
    glVertex2f(150, 50);
    glTexCoord2f(0, 1);
    glVertex2f(50, 50);
    glEnd();

As you can see, we again bind the texture we want to use. Then, while we are drawing the vertices of our rectangle, we also set texture coordinates, which tell OpenGL, how we want our texture to be projected onto the geometry.

To map a texture to any geometry, OpenGL uses texture coordinates. OpenGL will use relative coordinates around the edges of your texture, where the lower left corner of your image is (0, 0) and the upper right corner is (1, 1).

You can imagine this, as if OpenGL would stick needles through the texture at the points specified as texture coordinates and fixates those at the next vertex you draw.

Enabeling the textures

The code above should work, and we should now be able, to see our texture being rendered onto the rectangle. But unfortunately it remains white.

That is, because we haven’t told OpenGL to use textures in the scene yet. To do that, we need to add the following code to our reshape function:

    glEnable(GL_DEPTH_TEST);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable( GL_BLEND );
    glEnable(GL_TEXTURE_2D);

This tells OpenGL, to use textures and to blend then with the color values, using the alpha values of a texture.

Finally, a textured rectangle.

Textures on 3D objects

Wouldn’t it be cool, to have one of our two cubes textured as well? Let’s do that.

But before we get ready:

Housekeeping

Before we start diving into texturing our cube, we need to make a view simple refactorings, to be able to use the Geometry we already have defined in a textured environment, as well as in there current state.

Let’s have a look at the MyCube class and see how we can improve here.

The first thing you’ll note, is that currently all the drawing state is handled together with the vertex drawing in a single big and ugly drawing function. Let’s separate those two concerns for better readability.

Create a function called drawVertices that get’s called from within the normal draw function, so that the implementation will now look something like this:

- (void)drawVertices:(float)s{
    glBegin(GL_QUADS); 
    {
        //all the vertex drawings and color definitions....
    }
    glEnd();
}

-(void)draw{
    glPushMatrix();
    
    glTranslatef(positionX, positionY, positionZ);
    glRotatef([self rotationAngle], 1, 1, 1);
    float s = scale*.5;
    [self drawVertices:s];
    glPopMatrix();
}

The next thing we’d want to fix is the fact, that now we have to give the drawVertices function the scale parameter, so that it can represent size correctly. With OpenGL’s scaling functionality, we can fix that.

First we rewrite the drawVertices function, so that it will draw a default cube, regardless of scale:

- (void)drawVertices{
    glBegin(GL_QUADS); 
    {
        glColor3f(0, 0, 1);
        glVertex3f(-1,  1, -1); //F T L
        glColor3f(1, .75, 0);
        glVertex3f( 1,  1, -1); //F T R
        glColor3f(0, 1, 0);
        glVertex3f( 1, -1, -1); //F B R
        glColor3f(1, 0, 0);
        glVertex3f(-1, -1, -1); //F B L

        // and so on...
    }
    glEnd();
}

Then we’ll add an OpenGL command to scale the drawing matrix before we call the drawVertices function:

-(void)draw{
    glPushMatrix();
    
    glTranslatef(positionX, positionY, positionZ);
    glRotatef([self rotationAngle], 1, 1, 1);
    float s = scale*.5;
    glScalef(s, s, s);
    [self drawVertices];
    glPopMatrix();
}

Extra Credits: A Category for NSColor

One nice little detail we can also add, to make the coda a bit more readable and also tie it in a bit more with the Cocoa base classes, regards the way we set the OpenGL color attribute at the moment.

Since Cocoa comes with a pretty usable color class, we can create a category for that class, to be able to use it, when drawing to OpenGL.

Categories in Objective C allow you, to augment existing classes with new functionality, without subclassing them. That means, that we can add behavior to classes we do not need to own and use that new behavior on the base classes themselves.

This pattern is called a Mix-In and serves mainly two purposes:

  • It allows you, to keep your technical concerns separated in different source files, while still keeping the functional concern together on one single class.
  • It allows for a more elegant way, to write utility functions for basic types, that would only be possible with awkward wrapper objects otherwise.

We create a Category, the same way we create classes, by right-clicking in our project and choosing New File and then using the Objective C/Category type. The wizard will ask us about a name for our category and which class we want to augment.

Let’s choose “OpenGLEnabled” as the name and tell the wizard to augment the NSColor class.

After finishing the wizard, we get presented with a NSColor+OpenGLEnabled header and implementation file, which represents our new category.

Let’s add a function to that, which allows us to set any NSColor as the current OpenGL drawing color.

First add the new function setToOpenGL to the header:

@interface NSColor (OpenGLEnabled)

- (void) setToOpenGL;

@end

Then add it’s implementation to the implementation file:

@implementation NSColor (OpenGLEnabled)

- (void)setToOpenGL{
    NSColor* c = [self colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
    float red = [c redComponent];
    float green = [c greenComponent];
    float blue = [c blueComponent];
    float alpha = [c alphaComponent];
    
    glColor4f(red, green, blue, alpha);
}

@end

As you can see, we create a color representation of ourselves in an RGB color space, since we need that, to be able to call upon the red, green and blue components of the color.

After that, we simply call the glColor4f function like we normally would do.

Since the NSColor class doesn’t know some of the colors we want to keep using, we add static creators for that as well:

@interface NSColor (OpenGLEnabled)

+ (NSColor*) limeColor;
+ (NSColor*) maroonColor;
+ (NSColor*) navyColor;
+ (NSColor*) aquaColor;

- (void) setToOpenGL;

@end

And implement them:

+ (NSColor *)limeColor{
    return [NSColor colorWithDeviceRed:0 green:1 blue:0 alpha:1];
}

+ (NSColor *)maroonColor{
    return [NSColor colorWithDeviceRed:.5 green:0 blue:0 alpha:1];
}

+ (NSColor *)navyColor{
    return [NSColor colorWithDeviceRed:0 green:0 blue:.5 alpha:1];
}

+ (NSColor *)aquaColor{
    return [NSColor colorWithDeviceRed:0 green:1 blue:1 alpha:1];
}

Having this category allows us now, to rewrite the drawing function of the cube and make the code a bit more readable:

        [[NSColor blueColor] setToOpenGL];
        glVertex3f(-1,  1, -1); //F T L
        [[NSColor orangeColor] setToOpenGL];
        glVertex3f( 1,  1, -1); //F T R
        [[NSColor limeColor] setToOpenGL];
        glVertex3f( 1, -1, -1); //F B R
        [[NSColor redColor] setToOpenGL];
        glVertex3f(-1, -1, -1); //F B L

Now that looks a bit better now. There will be some additional refactoring in the later chapter, but for now, we’re good to go.

MyCube and MyRainbowCube

Since we want only the main cube, to use a texture and still want to have that rainbow color effect on the satellite, we should subclass our cube class, before we continue:

In the end, we want a basic cube, that draws itself in a single color, a rainbow cube that draws itself with the rainbow colors we know and a textured cube, that can take a texture and draw itself, using this texture.

Let’s first create a MyRainbowCube class, that extends the basic MyCube class and overwrites the drawVertices function with the (old) rainbow functionality.

#import <OpenGL/gl.h>
#import "NSColor+OpenGLEnabled.h"
#import "MyRainbowCube.h"

@implementation MyRainbowCube

- (void)drawVertices{
    glDisable(GL_TEXTURE_2D);
    glBegin(GL_QUADS); 
    {
        [[NSColor blueColor] setToOpenGL];
        glVertex3f(-1,  1, -1); //F T L
        [[NSColor orangeColor] setToOpenGL];
        glVertex3f( 1,  1, -1); //F T R
        [[NSColor limeColor] setToOpenGL];
        glVertex3f( 1, -1, -1); //F B R
        [[NSColor redColor] setToOpenGL];
        glVertex3f(-1, -1, -1); //F B L
        
        //and so on... you know the drill.
        
    }
    glEnd();
    glEnable(GL_TEXTURE_2D);
}

@end

Next, let’s alter the basic MyCube class, so that it has a base color attribute and draws itself, using only that color.

That means, that we add a property, holding the color in the header:

@property (assign) NSColor* baseColor;

…synthesize that property in the implementation file:

@synthesize baseColor;

…and finally change the drawVertices function like so:

- (void)drawVertices{
    if (!baseColor)
        [self setBaseColor:[NSColor grayColor]];
    
    glDisable(GL_TEXTURE_2D);
    glBegin(GL_QUADS); 
    {
        [baseColor setToOpenGL];
        glVertex3f(-1,  1, -1); //F T L
        glVertex3f( 1,  1, -1); //F T R
        glVertex3f( 1, -1, -1); //F B R
        glVertex3f(-1, -1, -1); //F B L
        
        glVertex3f(-1, -1, -1); //F B L   
        glVertex3f( 1, -1, -1); //F B R
        glVertex3f( 1, -1,  1); //B B R
        glVertex3f(-1, -1,  1); //B B L
        
        glVertex3f(-1,  1,  1); //B T L
        glVertex3f( 1,  1,  1); //B T R
        glVertex3f( 1, -1,  1); //B B R
        glVertex3f(-1, -1,  1); //B B L
        
        glVertex3f(-1,  1,  1); //B T L
        glVertex3f(-1,  1, -1); //F T L
        glVertex3f(-1, -1, -1); //F B L   
        glVertex3f(-1, -1,  1); //B B L
        
        glVertex3f(-1,  1,  1); //B T L
        glVertex3f( 1,  1,  1); //B T R
        glVertex3f( 1,  1, -1); //F T R
        glVertex3f(-1,  1, -1); //F T L
        
        glVertex3f( 1,  1, -1); //F T R
        glVertex3f( 1,  1,  1); //B T R
        glVertex3f( 1, -1,  1); //B B R
        glVertex3f( 1, -1, -1); //F B R
        
    }
    glEnd();
    glEnable(GL_TEXTURE_2D);
}

You see, that all the color stops are gone, and we just set the color at the beginning. We also define a default color, that get’s used, if no color has been defined.

Now, before we set out, to create the textured cube, let’s test if it worked.

In the implementation file of your MyOpenGLView, import your newly created MyRainbowCube and change the cube initialization in the awakeFromNIB method to the following:

    self.mainCube = [[MyCube alloc] init];
    self.flyingCube = [[MyRainbowCube alloc] init];
    [mainCube setScale:1];
    [mainCube setPositionX:0 Y:0 Z:0];
    [mainCube setBaseColor:[NSColor greenColor]];
    [flyingCube setScale:.2];
    [flyingCube setPositionX:5 Y:0 Z:0];

Now run your program. You should see the main cube sporting a fashionable green, while the satellite is still the old rainbow cube.

Building the textured Cube

Now, let’s create another sub-class of MyCube, to create a textured cube.

We call this one MyTexturedCube. It will need to overwrite the drawVertices function and have a property of type NSImage to hold the texture.

Other than that, we also need a local field called textureIndex of type GLuint that holds the OpenGL texture index.

Here is the header file:

#import "MyCube.h"
#import <OpenGL/gl.h>

@interface MyTexturedCube : MyCube {
    GLuint textureIndex;
    NSImage* _texture;
}

@property (assign) NSImage* texture;

@end

Take note, that we created a named local variable for the texture image as well. That is necessary, because we want to overwrite the setter functionality to also reset the textureIndex for OpenGL. Therefore we can’t rely on the @synthesize functionality, to create our getter and setter.

Now, let’s write the implementation file.

#import "MyTexturedCube.h"
#import "NSColor+OpenGLEnabled.h"

@implementation MyTexturedCube

- (id)init{
    self = [super init];
    textureIndex = -1;
    return self;
}

- (NSImage *)texture{
    return _texture;
}

- (void)setTexture:(NSImage *)texture{
    _texture = texture;
    textureIndex = -1;
}

- (void)drawVertices{
    
}

@end

First, we overwrite the init method, to set the textureIndex variable to a defined value of -1.

Then we write the getter and setter for our texture property. In the setter, we wan’t to also reset the OpenGL textureIndex, so that it will be reinitialized, when the next draw call comes around.

In the drawVertices function, we want to do three things:

  1. If we have a texture set, but no OpenGL texture index yet, we want to initialize the texture to OpenGL and set our textureIndex.
  2. If we have texture set, we want to bind that texture to the OpenGL context, so that OpenGL will attempt to use it.
  3. Last but not least, we want to draw our cube, while setting texture coordinates for the cube’s vertices.
- (void)drawVertices{
    if (_texture){
        if (-1 == textureIndex){
            NSBitmapImageRep* bmp = [[NSBitmapImageRep alloc] initWithData:
                                     [_texture TIFFRepresentation]];
            
            glGenTextures(1, &textureIndex);
            glBindTexture(GL_TEXTURE_2D, textureIndex);
            glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexImage2D(GL_TEXTURE_2D, 0, 
                         [bmp hasAlpha] ? GL_RGBA : GL_RGB, 
                         [_texture size].width, [_texture size].height, 0, 
                         [bmp hasAlpha] ? GL_RGBA : GL_RGB, 
                         GL_UNSIGNED_BYTE, 
                         [bmp bitmapData]);
        }
        glBindTexture(GL_TEXTURE_2D, textureIndex);
    }
    
    glBegin(GL_QUADS); 
    {
        [[self baseColor] setToOpenGL];
        glVertex3f(-1,  1, -1); //F T L
        glTexCoord2f(0, 0);
        glVertex3f( 1,  1, -1); //F T R
        glTexCoord2f(1, 0);
        glVertex3f( 1, -1, -1); //F B R
        glTexCoord2f(1, 1);
        glVertex3f(-1, -1, -1); //F B L
        glTexCoord2f(0, 1);
        
        glVertex3f(-1, -1, -1); //F B L   
        glTexCoord2f(0, 0);
        glVertex3f( 1, -1, -1); //F B R
        glTexCoord2f(1, 0);
        glVertex3f( 1, -1,  1); //B B R
        glTexCoord2f(1, 1);
        glVertex3f(-1, -1,  1); //B B L
        glTexCoord2f(0, 1);
        
        glVertex3f(-1,  1,  1); //B T L
        glTexCoord2f(0, 0);
        glVertex3f( 1,  1,  1); //B T R
        glTexCoord2f(1, 0);
        glVertex3f( 1, -1,  1); //B B R
        glTexCoord2f(1, 1);
        glVertex3f(-1, -1,  1); //B B L
        glTexCoord2f(0, 1);
        
        glVertex3f(-1,  1,  1); //B T L
        glTexCoord2f(0, 0);
        glVertex3f(-1,  1, -1); //F T L
        glTexCoord2f(1, 0);
        glVertex3f(-1, -1, -1); //F B L
        glTexCoord2f(1, 1);
        glVertex3f(-1, -1,  1); //B B L
        glTexCoord2f(0, 1);
        
        glVertex3f(-1,  1,  1); //B T L
        glTexCoord2f(0, 0);
        glVertex3f( 1,  1,  1); //B T R
        glTexCoord2f(1, 0);
        glVertex3f( 1,  1, -1); //F T R
        glTexCoord2f(1, 1);
        glVertex3f(-1,  1, -1); //F T L
        glTexCoord2f(0, 1);
        
        glVertex3f( 1,  1, -1); //F T R
        glTexCoord2f(0, 0);
        glVertex3f( 1,  1,  1); //B T R
        glTexCoord2f(1, 0);
        glVertex3f( 1, -1,  1); //B B R
        glTexCoord2f(1, 1);
        glVertex3f( 1, -1, -1); //F B R
        glTexCoord2f(0, 1);
        
    }
    glEnd();
}

By now, you should be able to understand that code pretty easily.


Now, let’s see if that worked. Add an image file to your projects Supporting Files and use it to draw a texture to your main cube. I used a texture called cube001_blue.jpg

To load it, change the awakeFromNib method of your view again, to look like this:

    self.mainCube = [[MyTexturedCube alloc] init];
    self.flyingCube = [[MyRainbowCube alloc] init];
    [mainCube setScale:1];
    [mainCube setPositionX:0 Y:0 Z:0];
    [mainCube setBaseColor:[NSColor whiteColor]];
    
    [(MyTexturedCube*)mainCube setTexture:[NSImage imageNamed:@"cube001_blue.jpg"]];
    [flyingCube setScale:.2];
    [flyingCube setPositionX:5 Y:0 Z:0];

Tadaa.

A bit more on Texture mapping

The observant reader will have noticed, that we took a bit of an easy way out there, since we simply mapped the whole image to every side of the cube.

But what, if we want to map different sides of the cube, to different texture images?

That’s where texture mapping comes in. We still use one image for the whole cube, but now we will define only parts of that image, to be mapped to a single side.

To do this, we first need to create a texture map, that contains all the sides we want to be drawn to the cube. You can have that image’s layout anyway you want, but it is good, to not be wasting any space, to not load more than needed into the GPU’s memory.


As you can see on the right, I choose a layout, where all the sides of the cube are arranged in two rows and three columns, making it easy to tell the different texture coordinates apart, as the column stops will be at 0.0, 0.333, 0.666 and 1.0 as well as the row stops at 0.0, 0.5 and 1.0.

If we simply were to load that image for a texture, we would still get the whole image stretched across each side of the cube, so we need now to change the mapping of our texture coordinates.

To be a bit flexible, regarding the mapping, we give the MyTexturedCube class a variable of texture coordinates as an array of floats and rewrite the drawVertices function to set the texture coordinates from that array.

In the header we add:

@interface MyTexturedCube : MyCube {
    GLuint textureIndex;
    NSImage* _texture;
    float textureCoords[48];
}

The new drawVertices will now look like this:

- (void)drawVertices{
    if (_texture){
        if (-1 == textureIndex){
            NSBitmapImageRep* bmp = [[NSBitmapImageRep alloc] initWithData:
                                     [_texture TIFFRepresentation]];
            
            glGenTextures(1, &textureIndex);
            glBindTexture(GL_TEXTURE_2D, textureIndex);
            glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexImage2D(GL_TEXTURE_2D, 0, 
                         [bmp hasAlpha] ? GL_RGBA : GL_RGB, 
                         [_texture size].width, [_texture size].height, 0, 
                         [bmp hasAlpha] ? GL_RGBA : GL_RGB, 
                         GL_UNSIGNED_BYTE, 
                         [bmp bitmapData]);
        }
        glBindTexture(GL_TEXTURE_2D, textureIndex);
    }
    
    glBegin(GL_QUADS); 
    {
        [[self baseColor] setToOpenGL];
        
        glTexCoord2f(textureCoords[ 0], textureCoords[ 1]);
        glVertex3f(-1,  1, -1); //F T L
        glTexCoord2f(textureCoords[ 2], textureCoords[ 3]);
        glVertex3f( 1,  1, -1); //F T R
        glTexCoord2f(textureCoords[ 4], textureCoords[ 5]);
        glVertex3f( 1, -1, -1); //F B R
        glTexCoord2f(textureCoords[ 6], textureCoords[ 7]);
        glVertex3f(-1, -1, -1); //F B L
        
        glTexCoord2f(textureCoords[ 8], textureCoords[ 9]);
        glVertex3f(-1, -1, -1); //F B L   
        glTexCoord2f(textureCoords[10], textureCoords[11]);
        glVertex3f( 1, -1, -1); //F B R
        glTexCoord2f(textureCoords[12], textureCoords[13]);
        glVertex3f( 1, -1,  1); //B B R
        glTexCoord2f(textureCoords[14], textureCoords[15]);
        glVertex3f(-1, -1,  1); //B B L
        
        glTexCoord2f(textureCoords[16], textureCoords[17]);
        glVertex3f(-1,  1,  1); //B T L
        glTexCoord2f(textureCoords[18], textureCoords[19]);
        glVertex3f( 1,  1,  1); //B T R
        glTexCoord2f(textureCoords[20], textureCoords[21]);
        glVertex3f( 1, -1,  1); //B B R
        glTexCoord2f(textureCoords[22], textureCoords[23]);
        glVertex3f(-1, -1,  1); //B B L
        
        glTexCoord2f(textureCoords[24], textureCoords[25]);
        glVertex3f(-1,  1,  1); //B T L
        glTexCoord2f(textureCoords[26], textureCoords[27]);
        glVertex3f(-1,  1, -1); //F T L
        glTexCoord2f(textureCoords[28], textureCoords[29]);
        glVertex3f(-1, -1, -1); //F B L
        glTexCoord2f(textureCoords[30], textureCoords[31]);
        glVertex3f(-1, -1,  1); //B B L
        
        glTexCoord2f(textureCoords[32], textureCoords[33]);
        glVertex3f(-1,  1,  1); //B T L
        glTexCoord2f(textureCoords[34], textureCoords[35]);
        glVertex3f( 1,  1,  1); //B T R
        glTexCoord2f(textureCoords[36], textureCoords[37]);
        glVertex3f( 1,  1, -1); //F T R
        glTexCoord2f(textureCoords[38], textureCoords[39]);
        glVertex3f(-1,  1, -1); //F T L
        
        
        glTexCoord2f(textureCoords[40], textureCoords[41]);
        glVertex3f( 1,  1, -1); //F T R
        glTexCoord2f(textureCoords[42], textureCoords[43]);
        glVertex3f( 1,  1,  1); //B T R
        glTexCoord2f(textureCoords[44], textureCoords[45]);
        glVertex3f( 1, -1,  1); //B B R
        glTexCoord2f(textureCoords[46], textureCoords[47]);
        glVertex3f( 1, -1, -1); //F B R
        
        
    }
    glEnd();
}

We still need to set the contents of this array to some meaningful value. Let’s add another setter for the texture for that, that also takes some coordinates:

In the header add:

- (void)setTexture:(NSImage *)texture withCoordinates:(float[48]) coords;

and implement that in the implementation file.

- (void)setTexture:(NSImage *)texture withCoordinates:(float*)coords{
    _texture = texture;
    textureIndex = -1;
    for (int i = 0; i < 48; i++) {
        textureCoords[i] = coords[i];
    }
}

Since we don’t want to bother the user of our class, to write down 48 float values, to set the coordinates with each texture, we can create some class level functions to help him with that. One, that sets the coordinates for using the whole picture on each side (as it was before), and one, to use the 3 by 2 mapping.

Header:

+ (float*)defaultTextureCoords;
+ (float*)threeByTwoCoords;

And the implementation:

+ (float*)defaultTextureCoords{
    float* coords = malloc(sizeof(float[48]));
    for (int i = 0;  i < 6; i++) {
        int x = 8 * i;
        coords[0+x] = 0.0f; coords[1+x] = 0.0f;
        coords[2+x] = 1.0f; coords[3+x] = 0.0f;
        coords[4+x] = 1.0f; coords[5+x] = 1.0f;
        coords[6+x] = 0.0f; coords[7+x] = 1.0f;
    }
    return coords;
}

+ (float *)threeByTwoCoords{
    float* coords = malloc(sizeof(float[48]));
    for (int i = 0;  i < 6; i++) {
        int row = i / 3; // 0, 0, 0, 1, 1, 1
        int col = i % 3; // 0, 1, 2, 0, 1, 2
        
        float ax = col/3.0;
        float ay = 1.0 / (row+1);
        float bx = (col+1)/3.0;
        float by = ay;
        float cx = bx;
        float cy = ay - .5;
        float dx = ax;
        float dy = cy;
        
        int x = 8 * i;
        coords[0+x] = dx; coords[1+x] = dy;
        coords[2+x] = cx; coords[3+x] = cy;
        coords[4+x] = bx; coords[5+x] = by;
        coords[6+x] = ax; coords[7+x] = ay;
    }
    return coords;
}

To save me some writing, rather than putting a specific value for each point, I calculated the values for each side in a for-loop.

Now we could set a texture with either simple or threeByTwo mapping coordinates. But we still need to fix our simple setter for the texture, since that would now produce undefined mappings.

Since we would most likely want to use the simple mapping, when we only set a texture image, we can rewrite the setter, to call setTexture:withCoordinates with the simple mapping and be done for.

- (void)setTexture:(NSImage *)texture{
    [self   setTexture:texture 
       withCoordinates:[MyTexturedCube defaultTextureCoords]];
}

That’s it. Let’s try it out. Include the texture map in your project and exchange the line in your view, where you set the cube’s texture with this:

    [(MyTexturedCube*)mainCube setTexture:[NSImage imageNamed:@"cube_tex.jpg"] 
                          withCoordinates:[MyTexturedCube threeByTwoCoords]];

Now that looks a lot better, doesn’t it?

Conclusion

Today, we learned, how to draw in OpenGL a 2D Perspective and added some textures to our scene. We learned, how OpenGL uses textures and how to map a texture to a 3D Geometry, using texture coordinates.

Next time, we will use some of that knowledge, to create a simple game menu, and learn how to draw text to OpenGL.

Also, since I moved my stuff to GitHub, you can download the demo code from there this time: Download the zip

Or fork the repository at: Github

Have fun and leave a comment, if you like.

So Long…

About these ads

3 thoughts on “Game Dev Diary 5: About Textures and 2D

  1. For some reason the sound is garbled? Also, if i want to change the raven.png to a different png file the picture becomes garbled. I really wish you’d continued this series, it’s one of the best i’ve seen, i’d really like to know about integrating menu’s and such.

    Many thanks

  2. Thank you very much for these great tutorials. I’d be very interested in one on how to use cocoa gui to make menu’s as well. Please make new tutorials, you’re awesome!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s