Sunday, May 31, 2009

Change of plans

Sometime when developing you come across an unexpected twist and new motivations. My latest "twist" is that for all intensive purpose the server part of my program is being put on hold. Trying to justify this with myself was hard because I feel that my only complete projects were ones I was forced to develop on the job. The new project I'm working on has several goals which I believe are more realistic then a "generic" mmo server.

Some of you may know about my 2d engine tutorial series on gpwiki.org . Since it has been released I've continued to get emails about it, more specifically how to compile/use the code rather than design. The whole purpose of those tutorials was to give people an idea of how to structure an engine rather than something to use. Some people have requested feature enchantments and others want to know when the next version is coming out. About a year ago I started on a new version of the engine. After awhile I was too busy at work and life in general and forgot about then abandoned it.

Since creating that project I've done a lot more engine design and coding and feel like it can be done better. Some of the bigger issues in my mind is that it uses immediate mode in OpenGL, which is slow and generally not that great to work with compared to VBOs and VAOs. The engine also isn't well documented nor structured in a consistent way and worst of all it carries some of my bad habits of using Get/Set in front of things where it isn't need and hungarian notation for private variables. I did delete the project but then the next day I decided that I will leave it up as a code sample and change the front page and license to reflect it's status.

So why the new engine? Besides the things listed in the last paragraph, I'm becoming more interested in modern graphics programming and 3D in general. I've done 3D and even a 3D engine for my current job however I haven't messed with modern features much such as VBOs, VAOs, shaders, FBOs which seem to be all the rage these days. Another big pushing factor for a new engine is to back-port what I do to my companies engine to make my life easier. Since my company is a startup I can really push the direct that the engine goes quite easily. The last point about the new engine is that it doesn't meaning that I have to give up the mmo idea, it just means I'm switching work from the server to the client. I still plan on making the server in scala as I feel it's suited well for that task but I also feel that C++ is better suited for the client (lack of game related libraries on java/scala).

Right now I've been working on the engine in my free time during the last week, I've put about ~15 hours into the project and feel it's coming along rather nicely. So far I've implemented input (keyboard, mouse), windowing, some math (vector2 & 3), vertex buffer formats, textures and vertex buffers. So far I feel that the VBO related stuff is the most interesting.

Right now the formats I support are:
  1. VertexNormalTexture
  2. VertexTexture
  3. VertexNormalColor
  4. VertexColor
They seem to be the common formats out there and if needed you can always plug-in your own formats if you want a custom one.

Here is the code for the vertex buffer:

/*
* File: vertexBuffer.hpp
* Author: Sean Chapel
*
* Created on May 30, 2009, 7:56 PM
* Copyright 2009 Seoushi Games. All rights reserved.
*/


#ifndef _VERTEXBUFFER_HPP
#define _VERTEXBUFFER_HPP


#include "vertexFormats.hpp"
#include "resource.hpp"
#include
#include "types.hpp"
#include "platform.hpp"


namespace k2e
{



/**
* A vertex buffer object that stores a fixed size of elements
*/

template <class T>
class VertexBuffer : public Resource
{

public:

/**
* A default constructor
*/

VertexBuffer()
{
}

/**
* A constructor with the number of elements and thier types
* @param numElements the number of elements to store
* @param type the type of vertex buffer
*/

VertexBuffer(unsigned int numElements)
{
SetSize(numElements);
}


/**
* Default destructor
*/

~VertexBuffer()
{
Dispose();
}

/**
* Sets the number of elements for the VBO
* @param numElements the number of elements to store
*/

void SetSize(unsigned int numElements)
{
buffer.clear();
buffer.reserve(numElements);
}

/**
* Adds an element
* @param v the element to add
*/

void AddElement(const T &element)
{
buffer.push_back(element);
}


/**
* Generates the buffer for use
*/

void Generate()
{
if(buffer.size() == 0)
{
return;
}

Dispose();

glGenBuffers( 1, &glId );
glBindBuffer( GL_ARRAY_BUFFER_ARB, glId );

glBufferData(GL_ARRAY_BUFFER_ARB,
buffer.size() * sizeof(buffer[0]),
&buffer[0], GL_STATIC_DRAW_ARB);

glUnmapBuffer( GL_ARRAY_BUFFER_ARB );
glBindBuffer( GL_ARRAY_BUFFER_ARB, 0 );

isResourceLoaded = true;
}


/**
* binds the buffer for use
*/

void Bind()
{
glBindBuffer(GL_ARRAY_BUFFER_ARB, glId);

T::SetupDraw();
}


/**
* Unbinds the buffer
*/

void Unbind()
{
T::TeardownDraw();
}


/**
* Implements the resource's dispose method
*/

virtual void Dispose()
{
if(isResourceLoaded)
{
glDeleteBuffers(1, &glId);
isResourceLoaded = false;
}
}

private:

u32 glId;
std::vector buffer;
};


} /* k2e */


#endif /* _VERTEXBUFFER_HPP */




If you noticed, every method is commented in doxygen format. It takes a lot long to code when adding all the comments up front but it also means that everything is documented as it progresses so people can actually use the code. All of my code so far has been commented like this and I feel it makes the code much nicer to work with and it makes me think of design before implementing something. When making my code I first design out all of the header file and make sure I'm not forgetting something then I'll comment the header then code. I find this method good pratice but it is more time consuming, fortunatly I don't have to hit crazy deadlines with this code.

Anyways about the code itself, you will notice it's templated which is something the kore2engine made little use of even though there are places that could definatly use it. It's also defined as a resource which means that I can easily have it managed by a resource manager (that's a topic for another blog post however).

Lets take a look at one of the texture formats that plug into the vertex buffer.


struct VertexTexture
{
/**
* A constructor that initializes all components
* @param position the verts position
* @param textureCoords the verts texture coordinatess
*/

VertexTexture(Vector3 position, Vector2 textureCoords)
{
this->position = position;
this->textureCoords = textureCoords;
}


/**
* Used for setuping up drawing with this format used by VertexBuffers
*/

static void SetupDraw()
{
int size = sizeof(Vector3) + sizeof(Vector2);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glVertexPointer(3, GL_FLOAT, size, (char*)NULL + 0);
glTexCoordPointer(2, GL_FLOAT, size, (char*)NULL + sizeof(Vector3));
}


/**
* Used for tearing down drawing with this format used by VertexBuffers
*/

static void TeardownDraw()
{
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


Vector3 position; /**<>
Vector2 textureCoords; /**<>
};


Nothing really too interesting, but it just goes to show how easy it is to make your own format.

So lets show some vertex buffers in use, in a simple application I'm using to test functionality and develop with I needed a hexagon grid. Here is the code to generate and draw a hexagon in a vbo.


HexGeometry::HexGeometry(float radius)
{
vbo.SetSize(8);

vbo.AddElement( k2e::VertexTexture(k2e::Vector3(0.0f, 0.0f, 0.0f),
k2e::Vector2(124.0f / 256.0f, 112.0f / 256.0f)));
vbo.AddElement( k2e::VertexTexture(k2e::Vector3(-radius/2.0f, radius, 0.0f),
k2e::Vector2(67.0f / 256.0f, 8.0f / 256.0f)));
vbo.AddElement( k2e::VertexTexture(k2e::Vector3(radius/2.0f, radius, 0.0f),
k2e::Vector2(189.0f / 256.0f, 8.0f / 256.0f)));
vbo.AddElement( k2e::VertexTexture(k2e::Vector3(radius, 0.0f, 0.0f),
k2e::Vector2(249.0f / 256.0f, 112.0f / 256.0f)));
vbo.AddElement( k2e::VertexTexture(k2e::Vector3(radius/2.0f, -radius, 0.0f),
k2e::Vector2(189.0f / 256.0f, 218.0f / 256.0f)));
vbo.AddElement( k2e::VertexTexture(k2e::Vector3(-radius/2.0f, -radius, 0.0f),
k2e::Vector2(67.0f / 256.0f, 218.0f / 256.0f)));
vbo.AddElement( k2e::VertexTexture(k2e::Vector3(-radius, 0.0f, 0.0f),
k2e::Vector2(7.0f / 256.0f, 112.0f / 256.0f)));
vbo.AddElement( k2e::VertexTexture(k2e::Vector3(-radius/2.0f, radius, 0.0f),
k2e::Vector2(67.0f / 256.0f, 8.0f / 256.0f)));

vbo.Generate();
}

HexGeometry::~HexGeometry()
{
}

void HexGeometry::Draw()
{
vbo.Bind();

glDrawArrays(GL_TRIANGLE_FAN, 0, 8);

vbo.Unbind();
}


The engine I wrote for my company would have been atleast three time as long because in that engine I decided to separate each component into their own buffer and force using an index array even if they were in order like above. Also at the moment that engine can only use GL_TRIANGLES, which made it easier to implement and use but less efficient along longer to setup. Overall I think this is pretty nice. Here is a screen shot from my test app with a grid of hexagons drawn with the above code.



Next time I'm unsure of what I will share and talk about, perhaps the resource manager would be a nice next step or possibly 2d graphics (My test app needs a gui).

Also I'm sorry about the code formating, since html ignores whitespace it makes it a pain to mess with. I've been using http://quickhighlighter.com/ and it looks great on their servers but this blog seems to mess it up for whatever reason, if someone has an idea on how to fix this let me know. Soon enough I'll publish all my code somewhere and I can just give links to a well formated html version.

1 comment:

  1. Nice to see more progress so soon. I think the ignoring of the beginning whitespace is a style problem. If Blogspot supports add-ons, there might be a syntax highlighter plugin out there that preserves indentation.

    Anyway, the app is looking good so far. I'll be anticipating the next iteration of your project.

    ReplyDelete