Infinitely Scrolling Tile-Based Map With External Textures

Morning all!

I am building a tile-based infinitely scrolling map. I already have an idea on how I am going to do that bouncing around in my head. My question is as follows: How can dynamically generated run-time textures? In other words, when a new tile is created at the edge of the scene, how can I node->SetMaterial() with a resource grabbed from an HTTP or WebSocket request?

The HTTP and WebSocket servers are already built and functioning just fine (I use them in another project); I am not asking about those. Just how, in Urho3D, to dynamically get those tile images onto a node.

Edit
The tiles are 256px by 256px PNGs.

Without knowing what you have from an HTTP or WebSocket request it’s kind of hard to know how to do it. If you have the png “file” stored in memory somewhere, you can construct a MemoryBuffer from the data, which you can then create an Image that you will call Load(memoryBuffer) to do the actual PNG loading. You can then use Texture2D::SetData(img,withAlpha) to set it as a texture. At the very least, I would suggest storing the texture in the ResourceCache as a manual resource.

I’m not certain, but I think it should also be possible to skip handling the Image yourself in the middle by just calling Texture2D::Load(memoryBuffer).

Awesome! Thanks. I will look into all of that. Looking forward to showing this thing off when I am finished.

We are literally talking about 2.345624806x10^13 tiles here. I can still use the ResourceCache for buffering but I will be adding and removing a lot of tiles in doing so. I will have to experiment with it.

Wait a second, I just realized I made an assumption that might affect the answer: This is a 3D game. When I said “tile” what I should have said was “PNG image.” I am looking to map these “tiles” onto block shapes to build out the world.

How many different images do you have? And are you asking how to get them onto models in the world, or do you have that figured out already?

2.345624806x10^13 possible images (there will be a lot of dynamically loading and unloading of images). I am asking how to get them onto models from memory (IE not from disk). Right now I am using Box.mdl as a test model.

Then my first answer is basically correct. You will also need to create Materials for each of the different images, by using void Material::SetTexture(TextureUnit unit, Texture* texture). You probably want TU_DIFFUSE as your unit. You then assign the box Static/AnimatedModel that material, and you should see your image.

If I get this right @nickwebha is going to stream in and stream out tiles so I would recommend to reuse material and texture objects when possible instead of creating new one every time.

The documentation does not say what a TextureUnit is. For example, the signature of SetTexture is SetTexture(TextureUnit unit, *texture). Also based on the documentation for *texture I do not see a way to get an image on there.

Is there a sample you can point me to doing something similar? I have looked but did not find what I was looking for.

Thanks.

TextureUnit is an enum defined at Urho3D/GraphicsDefs.h at master · urho3d/Urho3D · GitHub which just gives enum name handles to the various commonly-used texture types in the default shaders. The awkward part of this scheme is that if you are not using the default shaders, or you are using heavily-edited shaders, you still need to use a member of TextureUnit to designate which slot they’re bound to, even if the name doesn’t match up to your usage. So to bind to texture slot 0, you’d call SetTexture with TU_DIFFUSE, for example.

You don’t pass an image to SetTexture, you pass a Texture. @SirNate0 already mentioned how to get the Image data into the Texture2D, by way of Texture2D::SetData which, when passed an Image, will construct the texture from the image. Your job will be to create the Image, pass it to a Texture2D by SetData, and bind that texture to the appropriate texture slot in the shader.

I am a little stuck. I have gotten this far:

auto tile = GetSubsystem< Tile >();
std::string tileString = tile->GetTile( 0, 0, 0 );
const char* tileData = tileString.c_str();
MemoryBuffer tileMemoryBuffer( (void*)tileData, tileString.size() );
Image* tileImage = new Image( context_ );
tileImage->SetData( tileMemoryBuffer.GetData() );
Texture2D* tileTexture2D = new Texture2D( context_ );
tileTexture2D->SetData( tileImage );

Seems tileData contains a null terminating character and that might be throwing it off?

I have confirm GetTile() is returning the PNG image. Urho3D spits out the error

ERROR: Zero or negative texture dimensions

to the console.

Edit
I have not gotten to the part where I stick it on the node yet.

You need to set the Image size, it can’t determine it from the data. You may also need to set it on the texture, but I think using an Image for SetData takes care of that automatically (hopefully).

Also, I would avoid the conversation to string, as you will probably end up with issues if there are any 0 values in the data.

If your memory contains an PNG file in its entirety and not just the pixels extracted from one then you need to load it from the memory buffer:

tileImage->Load(tileMemoryBuffer);

not Image::SetData.

Edit: If the above is the case than you can just do this with your texture too, tileTexture2D->Load(tileMemoryBuffer);

1 Like

Is it just me or is there a dearth of documentation on materials and using materials?

Right now I have

auto tile = GetSubsystem< Tile >();
std::string tileString = tile->GetTile( 0, 0, 0 );
const char* tileData = tileString.c_str();
MemoryBuffer tileMemoryBuffer( (void*)tileData, tileString.size() );
Image* tileImage = new Image( context_ );
tileImage->SetSize( MAP_TILE_SIZE, MAP_TILE_SIZE, 3 );
tileImage->Load( tileMemoryBuffer );
Texture2D* tileTexture2D = new Texture2D( context_ );
tileTexture2D->SetData( tileImage );

SharedPtr< Node > tileNode_;
tileNode_ = scene_->CreateChild( "tile" );
tileNode_->SetPosition( Vector3( 0, 3, 0 ) );
tileNode_->SetScale( Vector3( 1, 1, 1 ) );
StaticModel* tileObject = tileNode_->CreateComponent< StaticModel >();
tileObject->SetModel( cache->GetResource< Model >( "Models/Box.mdl" ) );
tileObject->SetMaterial( TU_DIFFUSE, tileTexture2D );

(Of course that last line throws an error because it expects an XML or JSON document instead of a 2D texture.)

It seems to me Urho3D demands a file be loaded from disk. I can create all the MemoryBuffers I like but it is always going to ask for a material? I read all your guys responses (thanks again!) and I do not see how the code ends. How do I create a material on the fly at runtime?

@SirNate0
I could not find a way around using the C-style string so I used the constructor that lets you specify size instead. I think that should fix that issue (if I was even seeing that).

@JSandusky
Thanks for the heads up. When I get this working I will shorten the code.

Edit
Re-reading your replies again I feel like I am overlooking something here. What am I glossing over?

SetMaterial requires just a pointer to a material. TU_DIFFUSE is a texture unit specifier used for binding a texture to a specific slot inside a material. You could either manually create a material using new, and manually set up the techniques and passes, or just load an xml material definition, then manually bind your texture to the slot using Material::SetTexture(TU_DIFFUSE, tileTexture2d). But yes, a model is always going to need a Material, since a material specifies the techniques to use and shaders to use to render.

1 Like

This is not right, either:

const char* tileData = tileString.c_str();
Urho3D::MemoryBuffer tileMemoryBuffer( (void*)tileData, tileString.size() );
Urho3D::Image* tileImage = new Urho3D::Image( context_ );
tileImage->SetSize( MAP_TILE_SIZE, MAP_TILE_SIZE, 3 );
tileImage->Load( tileMemoryBuffer );
Urho3D::Texture2D* tileTexture2D = new Urho3D::Texture2D( context_ );
tileTexture2D->SetData( tileImage );

Urho3D::SharedPtr< Urho3D::Node > tileNode_;
tileNode_ = scene_->CreateChild( "tile" );
tileNode_->SetPosition( Urho3D::Vector3( 0, 3, 0 ) );
tileNode_->SetScale( Urho3D::Vector3( 2, 1, 2 ) );
Urho3D::StaticModel* tileObject = tileNode_->CreateComponent< Urho3D::StaticModel >();
Urho3D::Material* tileMaterial = new Urho3D::Material( context_ );
tileObject->SetModel( cache->GetResource< Urho3D::Model >( "Models/Box.mdl" ) );
tileMaterial->SetTexture( Urho3D::TU_DIFFUSE, tileTexture2D );
tileObject->SetMaterial( tileMaterial );

No errors from the compiler so there is that.

Edit
I checked the return values and they all came back as 1. Where as before it was complaining about an unrecognised image format when I was accidently loading the wrong thing. So I know that the error catcher works.

Well, it’s not going to work just creating a blank material. If you read the documentation for Materials, you can see that the XML format provides a lot of different tags for passes, techniques, cull settings, etc… If you elect to not define your material via XML, then you need to duplicate those necessary setups in code. For example, if you don’t provide a technique, it will fall-back to a default which is a NoTexture technique, meaning no textures will be applied. You can see what the Material defaults look like here. You need to define your material so that it renders your object exactly as you need it, including technique which is pretty important. The easiest way to do it is to define the material XML, load it from the resource cache and assign it to the object. Then you can bind your texture to the material texture slot it needs to go. But if you really can’t/won’t do that for some reason, you can manually create a Technique that does what you need it to do.

I think I understand something now I did not before. It also sounds like I can use a single XML file for all 2.345624806x10^13 tiles, too.

Thank you. Hopefully I will not be back. :slight_smile:

As an aside, doing something like Urho3D::Material* tileMaterial = new Urho3D::Material( context_ ); is rather a bad practice, since it could potentially leak. Instead, you should explicitly assign it to a SharedPtr during the allocation. Of course, this means you need to be mindful about keeping it alive for as long as is needed, since inside Batch (which is used by StaticModel) the Material pointers are stored as raw pointers, meaning if your SharedPtr dies from going out of scope, those raw pointers now point to dead memory. Some parts of Urho store SharedPtr internally, but some don’t. In the case of Materials, the usual use case is that ResourceCache manages its lifetime, so if you bypass ResourceCache and allocate Material directly, you need to be mindful of keeping it alive for as long as needed and freeing it when no longer needed.

I am experimenting with creating a single Materials XML file that will be used with all tiles and then supplying the image via std::string.c_str() instead of using <texture />. I am trying to do things within the framework (defining my material via XML, for example) but mixing that with the MemoryBuffer is tricky (at least to someone like me who is still learning).

Normally I pick apart the Samples-- which are a great resource-- but there is not one that does this. For example, how do I set
tileObject->SetMaterial( cache->GetResource< Urho3D::Material >( "Materials/Tile.xml" ) );
(without <texture /> in it) and
tileObject->SetMaterial( tileMaterial );
at the same time to set everything via XML except the image itself? The obvious answer is you do not but I do not know of any alternatives. SetMaterial is how you set a material so mixing XML files with buffered data seems impossible from where I am sitting. I know you have explained a lot but the more I look the less is clicking for me without the code sample (not that I am asking you to provide one).

Edit
The Material in the XML file defines a lot of things besides the image itself. How do you get the image on there while still using the XML file? My brain is just two stones rubbing together it feels like.