Parse a texture type from a text file

Started by
9 comments, last by gothicly 14 years, 3 months ago
So for the past week I have been doing my best to create a way to load my game from a map file. So far, so good actually, except I have run into one little snag. I am trying to use my text file to tell my game which LPDIRECT3DTEXTURE9 to use, and it will not work. On a related note, I decided to use a third party parser called Daabli, and it works great except for the fact that it will not parse the texture name. So far I have gotten it to use a string that converts to a LPCSTR to get the texture location, but I have no idea of how to get the LPDIRECT3DTEXTURE9 to load... I tried a reinterpret_cast<LPDIRECT3DTEXTURE9>(with an lpcstr) and it returns an error. I have a second text file that loads the sets of textures, but since I can't parse the LPDIRECT3DTEXTURE9 from the file I can't load the set of textures I want... I have the feeling that I'm going about this the wrong way... So any guidance would be incredibly helpful! File looks as such: { {x=0.0;y=0.0;z=0.0;texdword=0;startvert=0;endvert=2;Texture = "g_pTexture";}, {x=0.0;y=0.0;z=0.0;texdword=0;startvert=4;endvert=2;Texture = "g_pTexture";}, etc... } the "g_pTexture" does do nothing, so I hard code the texture name for now... texture file: { {Location = "textures/ground/ground01.bmp"; Texture = g_pTexture;} } The second param is supposed to create a texture with the Location... and does not due to the fact that it will not parse the second param. I do know this because I made a simple parser in the console with Daabli, and it just says that the pointer is unrecognized... My two ideas are: Either make it parse the file straight with LPDIRECT3DTEXTURE9, or parse file and convert the types. Ideas?
Advertisement
Quote:Original post by gothicly
So far I have gotten it to use a string that converts to a LPCSTR to get the texture location, but I have no idea of how to get the LPDIRECT3DTEXTURE9 to load... I tried a reinterpret_cast<LPDIRECT3DTEXTURE9>(with an lpcstr) and it returns an error.

That's not how you load textures :) Right now you only have the filename of the texture you want. You then need to explicitly load the texture data using something like D3DX's D3DXCreateTextureFromFile function. See the 'Loading A Texture' section of this tutorial.

It sounds like that's the missing link in your set up, so hopefully it all falls into place from there.
What I mean, is that I have a string for the loading of the location, and I was parsing the other variable, and it wouldn't work, so I tried using a lpcstr and it did not work... So, I guess my main question would be, how do you parse the LPDIRECT3DTEXTURE9 for a text file?

I can load textures hard coded fine, it's just specifying multiple ones in external files that is the problem.
If you have the filename of the texture in a string, and have the ability to load textures from hard-coded locations, it should just be a matter of using the same texture loading code you are currently using, but supplying it with the previously mentioned filename string. There should otherwise be no difference in operation.

Not sure what you mean by "parse the LPDIRECT3DTEXTURE9 for a text file".. generally you only store image filenames and some other associated information for textures in game data files, like you are already doing.

The only thing I can see otherwise is that you might be missing "s in your second file, the texture one:
Quote:
texture file:
{
{Location = "textures/ground/ground01.bmp"; Texture = "g_pTexture";}
}



FAKE EDIT: Oh, I think I see what you're getting at now. You are asking how to associate the textures mentioned in the objects (?) in your first file to the filenames in the second file?

If so, specifying something like "g_pTexture" or whatever variable name in your config file alone won't do what you need - you need to handle this storage/association part yourself.

What you could do is have a texture file like this (never used Daabli before, so bear with any silly mistakes):
{  {Location = "textures/ground/ground01.bmp"; ID = "ground1"; },  {Location = "textures/ground/ground02.bmp"; ID = "ground2"; },  {Location = "textures/something/whatever.bmp"; ID = "whatever"; },  ... etc ...}


.. and have a object file like this:
{  {x=0.0;y=0.0;z=0.0;texdword=0;startvert=0;endvert=2;Texture = "ground1";},  {x=0.0;y=0.0;z=0.0;texdword=0;startvert=4;endvert=2;Texture = "whatever";},  ... etc ...}


So you have a list of texture information (a filename and ID for each), and game objects can refer to what texture they need by using one of those IDs.

At runtime, you load/parse the texture info file. You then go through each of the entries, loading the image from the specified filename using whatever texture loading mechanism (i.e., using D3DX as mentioned before), and store the LPDIRECT3DTEXTURE9 in a std::map, with the keys being the texture IDs.

When you are loading the game objects, you can look up the texture IDs of each in that std::map and retrieve a LPDIRECT3DTEXTURE9 for each. Each texture will only be loaded once, and objects that need the same texture can share the same LPDIRECT3DTEXTURE9.
Quote:Original post by mattd
So you have a list of texture information (a filename and ID for each), and game objects can refer to what texture they need by using one of those IDs.

At runtime, you load/parse the texture info file. You then go through each of the entries, loading the image from the specified filename using whatever texture loading mechanism (i.e., using D3DX as mentioned before), and store the LPDIRECT3DTEXTURE9 in a std::map, with the keys being the texture IDs.

When you are loading the game objects, you can look up the texture IDs of each in that std::map and retrieve a LPDIRECT3DTEXTURE9 for each. Each texture will only be loaded once, and objects that need the same texture can share the same LPDIRECT3DTEXTURE9.

I obviously agree with your post, but I'd like to add another option which looks easier and faster.

If OP used numbers instead of strings...


{
{Location = "textures/ground/ground01.bmp"; ID = 0; },
{Location = "textures/ground/ground02.bmp"; ID = 1; },
{Location = "textures/something/whatever.bmp"; ID = 2; },
... etc ...
}

... there would be no need to store a string in the object file:

{  {x=0.0;y=0.0;z=0.0;texdword=0;startvert=0;endvert=2;Texture = 0;},  {x=0.0;y=0.0;z=0.0;texdword=0;startvert=4;endvert=2;Texture = 2;},  ... etc ...}


This way OP could use a simple std::vector instead of a std::map.

Furthermore, the ID could be made implicit if the loading order is fixed.

So, it would be even simpler:

1- load all textures without IDs, just adding them to a texture vector
2- get the texture ID from the object file and use it to load the correct texture from the texture vector
The only problem with using a std::vector is maintainability.

Adding new textures would be OK, just add them with the next highest ID available (or at the end of the list if using the implicit numbering idea).

When you want to remove textures however, you end up with a gap in the ID numbering, and wasted space in the std::vector. One gap might not be a problem, but it could easily add up, leading to a fragmented ID space as development of the game content progresses. You could of course fill in the gaps by renumbering the IDs to be contiguous again, but then you have the problem of having to renumber the texture IDs in the objects file too.

Implicit numbering has the last problem when removing textures too - all the textures past the removed one shift up one location, meaning you need to renumber the object texture IDs.
Daabli will not create your DirectX texture for you and pass back a pointer; you'll have to do that yourself, as mattd has mentioned above.

Some pseudocode:

If you have your object defined like this:
// Object information structurestruct Object{    float              _x, _y, _z;    int                _texdword;    int                _startvert, _endvert;    std::string        _textureName;        LPDIRECT3DTEXTURE9 _pTexture;    const bool Read(Daabli::Reader &r)    {        _pTexture = 0;        return            r.Read( "x",           _x           ) &&            r.Read( "y",           _y           ) &&            r.Read( "z",           _z           ) &&            r.Read( "texdword",    _texdword    ) &&            r.Read( "startvert",   _startvert   ) &&            r.Read( "endvert",     _endvert     ) &&            r.Read( "textureName", _textureName );    }};


And your texture defined like this:
// Texture information structurestruct TextureInfo{    std::string _location;    std::string _textureName;    const bool Read(Daabli::Reader &r)    {        return            r.Read( "location",    _location ) &&            r.Read( "textureName", _textureName );    }};


Then you could read your textures and objects and associate them like this:
int main(int /*argc*/, char * /*argv*/[]){    // Read the textures    std::map<std::string, LPDIRECT3DTEXTURE9> texturesMap;    {        Daabli::Reader r;        if( !r.FromFile( "textures.txt" ) )            return -1;        std::list<TextureInfo> textures;        if( !r.Read( "textures", textures ) )            return -1;        // Create the LPDIRECT3DTEXTURE9 textures from the file names        for(std::list<TextureInfo>::const_iterator itr = textures.begin();            itr != textures.end(); ++itr)        {            LPDIRECT3DTEXTURE9 pTexture = 0;            // Create the texture from the filename            //D3DXCreateTextureFromFile(            //    pDevice,                  // d3d device            //    (*itr)._location.c_str(), // filename            //    &pTexture );              // returned LPDIRECT3DTEXTURE9            texturesMap.insert( std::pair<std::string, LPDIRECT3DTEXTURE9>(                (*itr)._textureName, pTexture ) );        }    }    // Read the objects    std::list<Object> objects;    {        Daabli::Reader r;        if( !r.FromFile( "objects.txt" ) )            return -1;        if( !r.Read( "objects", objects ) )            return -1;        // Assign the LPDIRECT3DTEXTURE9 textures to the objects        for(std::list<Object>::iterator itr = objects.begin();            itr != objects.end(); ++itr)        {            (*itr)._pTexture = texturesMap[ (*itr)._textureName ];        }    }    // Use 'objects' here...    return 0;}


Where textures.txt could look like this:
textures ={    { location = "ground.jpg"; textureName = "groundTex"; },    { location = "wall.jpg";   textureName = "wallTex";   }};


And objects.txt could look like this:
objects ={    { x=0.0; y=0.0; z=0.0; texdword=0; startvert=0; endvert=2; textureName = "groundTex"; },    { x=0.0; y=0.0; z=0.0; texdword=0; startvert=4; endvert=2; textureName = "wallTex"; }};
Quote:Original post by mattd
The only problem with using a std::vector is maintainability.

Adding new textures would be OK, just add them with the next highest ID available (or at the end of the list if using the implicit numbering idea).

When you want to remove textures however, you end up with a gap in the ID numbering, and wasted space in the std::vector. One gap might not be a problem, but it could easily add up, leading to a fragmented ID space as development of the game content progresses. You could of course fill in the gaps by renumbering the IDs to be contiguous again, but then you have the problem of having to renumber the texture IDs in the objects file too.

Implicit numbering has the last problem when removing textures too - all the textures past the removed one shift up one location, meaning you need to renumber the object texture IDs.

You're right, no doubt about that!

IMHO those mainainability issues depend on how OP is going to load a level and the level of abstraction his "game level" has.

From my POV one thing is to have a texture cacher/manager, another thing is to provide a list of textures for fast rendering of a game level.

To my experience (but I might be wrong) the best thing to do is to always ask the cacher/manager to load textures (and store them in a map), using the full file path as a key (in case of a texture cacher with a vector, you can use a filename class member).

The operations on textures are done via the manager (loading/unloading), while rendering is performed on a texture vector associated with that level.

In theory, before loading each "level", the texture vector should be cleared.
When a texture has to be loaded, we should ask the cacher to load it (if it's already loaded a valid * is already available to the cacher).

If a texture can't be loaded (missing file or something else) the manager will return 0, and we can skip those invalid "object/tiles" with a simple test.

Of course this thing is going to fail when you remove a texture from the manager, as the texture pointer stored in texture vector will still point at an invalid address.

I can't see why somebody would want to unload a game level texture while the game is running, neither I expect this "feature" to produce something good onscreen... but nobody forbids OP to do something like that:

mytexture *pTex = texturevector;pTextureManager->Remove(pTex);texturevector = 0;


In that case all tiles/objects whose ID is 'i' will just disappear and the game will not crash.

What's good is OP can do something like that easily:

texturevector = pTextureManager->GetTexture(".//FooBar.jpg");


or

texturevector = pTextureManager->GetTexturebyID(j);


To sum up, my POV is a game level can safely access textures using relative (or implicit) IDs, since it is likely OP will need an higher level object (a cacher/manager) to do the dirty work of resource loading/releasing.

In general I prefer vectors because the memory footprint of a map is more "obscure"...
The higher-level texture cacher is a good idea.

For clarification, my previous post was in the context of removing textures during development, not during runtime. For example, you might decide a texture was no longer needed in your game (maps/objects had been redesigned by an artist) and so you remove the entry from your texture config file.

If this texture entry removal was done within an editor of some sort, numeric IDs pose no problem since the application can then quickly renumber any affected object texture IDs and write out the new map file. But if you're doing it by hand, it's not so nice.
I agree, removing textures by hand when using a vector is going to be a nightmare!

[wink]

This topic is closed to new replies.

Advertisement