Tuesday, July 12, 2011

Top Models

With everything I've done, it's about time I start getting to drawing something to the screen. Before I can do that, however, I have to define what it is I'm going to draw. The shapes that describe the different objects in the world are called models.

Models in three dimensions are composed of triangles, and these are made up of three vertices each. A vertex (singular of vertices) is described by several numbers. We need it's position in space, which is described by three coordinates (x,y,z). We also need the normal for each point, which is used in lighting calculations and describes a direction perpendicular to the surface. This vector is also described by three values and should be of unit length. We can specify a color for each point, which requires the color values (RGB, greyscale, transparency, etc). Finally we have the texture coordinates, which describe the point on a texture that should be mapped to the vertex. Since textures are flat, this is accomplished with just two values, (u,v).

This means there are between nine and twelve values that describe a point. These values can be stored on the video card itself, using structures called Vertex Buffer Objects. Each of these objects can be called with a unique ID called names, which OpenGL provides when you request the creation of one.

This means that my model structure in the program needs to know only the VBO name.

When storing the vertex positions, however, I am doing it in the model coordinates. Depending on how I translate them to screen coordinates, I could scale or stretch the model. If I want to draw a large sphere and a small sphere, I could either store the points for each, or I could store the points for a unit sphere and scale it to the size I want.

Additionally, I could have a model consisting of different sub-models. For example, one could create a snowman out of reusing the model for a sphere, instead of a single model storing the values of two spheres. In order to do this, the snowman model would need the name of the basic sphere and two matrices, to scale and translate the spheres to the correct shape and position.

This implies two kinds of models. The primitives would consist of the Vertex Buffer Object names, while the complex ones would consist of a list of primitives and matrices applied to them. Of course, the list could also contain other complex models itself. These two types could be condensed if we stored both properties in every model. Primitive types would hold empty lists, while the VBO names for complex types would be null.

The models would then be stored in the model cache, and different entities would call them when needed.

I would still need methods for loading the vertex information into the buffers, however, and for drawing them. But this is something to look into in another post.

Saturday, July 9, 2011

Event handling done, and a bit about File I/O

Well, the bare-bones version of the event handling system is done. There are some improvements to make, and I only support a few keys and commands still, but the system is fairly robust and extensible. Plus, the key mappings are read from configuration files now, so they can be modified without recompiling.

The shape it has taken, in the end, is quite close to my original design. I created an Event Interpreter, which receives the system events and returns a command. In order to do this, it has a list of contexts where it searches for the event. Each context consists of a map, using events as the key.

The order of contexts is important, since an event can have different meanings in each. The contexts closer to the top have priority, however, and they override the commands in lower contexts. During the course of the program, the contexts may shift up and down, or even be removed completely and held in a holding stack in order to disable a set of commands at once.

In order to load the contexts in the begining, the program reads a text configuration file. The file is structured such that contexts are marked by curly braces ({}), and are composed of lines delimited by semi colons (;). Each line consists of an Event:Command pair, separated by a colon (:). The properties of each event and command, which identify them, are set apart by commas. So a simplex context would look like

{EventType, EventCode, : CommandType, CommandCode,;}

I'll probably end up adding an identifier for the Context. As it stands, I can comment anywhere between Contexts, but I have to be careful about what I write inside them. I should add a validation code of some kind. A missing file can hang the program right now.

I'm pretty happy about how the file reading code came out. String manipulation in C++ isn't trivial, whic is one of the reasons why everyone usually ends up rolling their own string class. I managed to deal with it fairly cleanly, though. I can't handle encodings other than ANSI (maybe UTF-8, haven't tested), but for a configuration file this shouldn't be a problem.

Now that this is done, however, I need to consider what else needs to be done. I think it's about time I gave the drawing code another look. I can work on other parts, but for testing purposes being able to see the effect of things in the screen would be a lot more useful. I'm hoping to be able to create assets in real time, which is one of the main drives in the project, and I need to be able to see the output ahead of everything else.

This means giving the resource caches another look as well. So I'll be having some fun.

Monday, July 4, 2011

Event Handling

So back to the matter of Events. These represent the interactions the player can have with the program. On the one hand, you have everything the player can do with the computer; pressing keys, moving the mouse, etc. On the other, you have the actions you can assign to one or more of these keys; moving the character, using items, shooting, etc.

The SFML already provides me with the player events. What I have to do is create the actions that correspond to them. I would then have a function that is fed an event and outputs the correct action. I'll start with the most basic event: a command to close the window and end the execution of the program. I'll provide the 'Esc' button for it.

After reading the events and generating the associated commands, I have to execute the command. For the most part, these commands are going to represent state changes in the game, and not so much actions themselves. In the case of a move forward command, pressing the 'move forward' key would toggle the entity's state to 'is moving forward', and releasing it would reset it to 'is stopped'. Then in the update loop the game would check if the player 'is moving forward' and if so advance it's position.

Of course, if I have each command execute itself from the event loop, I have to riddle the entity interface, and anything the player can interact with, with functions to access the members that define their state. Instead what I'll do is read the command, build a command queue for each target that might receive a command, and pass the stack to the target. That way the target (be it an entity, the window or whatever) can apply the commands internally.

I'll start with some basic commands: 'Esc' to close the game, and 'WASD' to move forward and back and strafe left and right. I'll use 'Esc' on press, meaning as soon as the key is pressed the program will end. The four keys will actually mean eight commands. Four to start and four to stop, on press and on release respectively. This allows to have several keys pressed at once, and if opposite keys are pressed, they can both be applied (it would result in no effect at all, but it wouldn't bother the game).

 My command class is going to mirror the structure of the event class provided by SFML. It will consist of an enumeration describing the kind of command it is, such as a window or movement command, and a union of structs that each describe the kind of command in detail.

The Commands are mapped to Events in structures called Contexts. Each context represents a set of Commands that are available. We do this because particular keys could have different meanings depending on the state of the game. For example, it could be that when the inventory is open, the movement commands are disabled. The contexts give the ability to remap keys and alter the available commands on the fly.

Well, this gives a bit of an overview of how I'll be handling user input. Next up, I'll be describing the process in more detail as I finish writing it up. I have the basic structures coded now, but I still need to assemble it all together.

Saturday, July 2, 2011

Crappy week

Caught a cold during the weekend, and I just couldn't get anything done during the week. A few late nights didn't help, either. So, I managed to look at event handling a bit, got some promising ideas, but that's not what I'll be working on just now.

I am going to, instead, work a little more on the resources, wrap that up for now, then move on. I had a few interesting thoughts before, and then someone pointed out that keeping a pointer and a count of times it's been referenced is a lot like having a smart pointer. Boost offers a number of them, of which 'shared_ptr' seemed to be the most appropriate. The way they work is that, so long as I refrain from using regular pointers, I should not have dangling pointers (pointers aimed at a resource I already freed) because the pointers share a counter themselves. Only once all references to a memory address have been lost is the memory freed.

So what I'll do is, when reading a file, create a resource and point to it with a shared pointer. I then store the shared pointer in a map, and key it to a string value (for now, just the name of the file). Then I can have anyone who needs it get a copy of the shared pointer, and keep track of how many people hold pointers to each value. Then instead of using handles to get a pointer (which I didn't do to avoid the problems with naked pointers in the first place), I can use the smart pointer itself.

For now I'll give the cache limited functionality. It can load a pointer, given a string to key it with, and it can produce the pointer, given the key. The other cool thing about smart pointers is that I don't need to worry so much about erasing things just yet. Not only do they not erase the resource until it's not needed by the last reference, but when the last reference to it dies one way or another, it is deleted automatically as well. So no memory leaks. Of course, since the cache holds a reference, the resources won't be freed unless I specifically say so.

The matter of reading a file is still something I'm struggling with. There are several ways of going about it. I'll do the more flexible one for now, and consider how to refactor the code later. What I'll be doing is storing all the code for turning files into resources in one class, the caches to store the resources as another class, and the code to call them in the framework class that holds them both.

There, back in business. And hey, these changes don't even require that I change (too much) my previous code! The file reading code just needs to be updated to provide shared pointers instead of naked pointers. So I should be able to test that the changes don't break anything easily enough.

Anyway, it's been too long without any pretty drawings, so I'm going to be delving into some stuff way off in the future in the next few posts, while I silently toil at the boring backbone stuff.