Infinitely Scrolling Tile-Based Map With External Textures

You only have to define a texture tag inside the material definition if you want a texture to be bound to a slot automatically when the resource is loaded. You can still call Material::SetTexture to explicitly bind a texture to a material, even one you created by loading an XML definition. Even if you do assign a texture inside the material XML, you can still override/overwrite it using SetTexture.

You setup your XML so that it specifies all the settings you need: cull mode, technique, passes, etc… Load it via ResourceCache::GetResource, then with the returned pointer you call SetTexture to set your texture to the appropriate slot. Then you can assign that Material pointer to as many models as you need.

Since ResourceCache is now managing the lifetime of your Material, you can call GetResource with the name of your material at any time to obtain another pointer to the same material instance. Then you can call Material::SetTexture on this pointer to change the bound texture for all instances of that material, meaning that you can perform whatever Tile initialization and construction you need to do simply by calling tileObject->SetMaterial, then after the fact or during runtime you can change the bound texture by obtaining another pointer to the Material from the cache.

Note that in this pattern, since all Tiles share a material instance, changing the texture bound to one will change the texture bound to all. If you do have Tiles that need a different texture, you will have to create separate Material instances for each texture.

@JTippetts1
Note that in this pattern, since all Tiles share a material instance, changing the texture bound to one will change the texture bound to all. If you do have Tiles that need a different texture, you will have to create separate Material instances for each texture.

Now that throws a wrench in the works. Since there are sssooo many tiles that is not practical for a number of reasons. Looks like I might have to define everything in code after all and just make sure I manage the memory properly. If I am understanding you. I do not think that was covered in the documentation.

Yeah, if you need different textures for tiles, you might need to manage them differently. ResourceCache keeps a map of Filename->Material pointer so that when you request a given XML material def, it will return the one already loaded.

However, you can still make use of the ResourceCache to load your initial material, if you don’t want to construct a Material by hand. Material provides a method called Clone which will return a SharedPtr to a Material that is an exact copy of the material. You can load your XML def (without a bound texture) using ResourceCache, then for every unique instance of the material you need, call Clone and store the returned SharedPtr in your own cache of some sort, ie a vector or somesuch.

I managed to figure it out!

For anyone looking in the future here is my code:

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 = cache->GetResource< Urho3D::Material >( "Materials/Tile.xml" );
tileObject->SetModel( cache->GetResource< Urho3D::Model >( "Models/Box.mdl" ) );
tileMaterial->SetTexture( Urho3D::TU_DIFFUSE, tileTexture2D );
tileObject->SetMaterial( tileMaterial );

The variable names should all be pretty self-explanatory. Note this is using “regular” pointers so that could be done better with smart pointers.

Tile.xml (based off Stone.xml):

<material>
	<technique name="Techniques/Diff.xml" quality="0" />
	<shader psdefines="PACKEDNORMAL" />
	<parameter name="MatSpecColor" value="0.3 0.3 0.3 16" />
</material>

Do not quote me on the XML part. Still learning about that. But it works.

Thank you, thank you, thank you guys for your help! Can not wait to stick this in the showcase when it is ready!

1 Like

I wrote a Bash script to generate an XML file for each tile. They (of course) all have different file paths (Materials/Tile.xml, Materials/6/0/0.xml, Materials/6/0/1.xml, etc).

Tile.xml, 0.xml, 1.xml, etc:

<material>
    <technique name="Techniques/Diff.xml" quality="0" />
</material>

I create the grid of tiles, each using a different XML file (Materials/6/0/0.xml, Materials/6/0/1.xml, etc). I then create a test floating block using Materials/Tile.xml and programmatically assign it a texture. They all change to that texture.

Here is my “floating test block”:

auto cache = GetSubsystem< Urho3D::ResourceCache >();

const char* tileData = tileString.c_str();
Urho3D::MemoryBuffer tileMemoryBuffer( (void*)tileData, tileString.size() );
Urho3D::SharedPtr< Urho3D::Texture2D > tileTexture2D;
tileTexture2D = new Urho3D::Texture2D( context_ );
tileTexture2D->Load( tileMemoryBuffer );

Urho3D::SharedPtr< Urho3D::Node > tileNode_;
Urho3D::SharedPtr< Urho3D::StaticModel > tileObject;
Urho3D::SharedPtr< Urho3D::Material > tileMaterial;
tileNode_ = scene_->CreateChild( "tile" );
tileNode_->SetPosition( Urho3D::Vector3( 0, 4, 0 ) );
tileNode_->SetRotation( Urho3D::Quaternion( 0, 90, 0 ) );
tileNode_->SetScale( Urho3D::Vector3( MAP_TILE_MULTIPLIER, 1, MAP_TILE_MULTIPLIER ) );
tileObject = tileNode_->CreateComponent< Urho3D::StaticModel >();
tileObject->SetModel( cache->GetResource< Urho3D::Model >( "Models/Box.mdl" ) );
tileMaterial = cache->GetResource< Urho3D::Material >( "Materials/6/0/0.xml" );
tileMaterial->SetTexture( Urho3D::TU_DIFFUSE, tileTexture2D );
tileObject->SetMaterial( tileMaterial );

It is my understanding that ResourceCache caches based on file hashes/file paths. Does it instead cache on something else and that is why they are all changing, not just the “floating test block”?

Did you actually assign the textures to the tiles? If you use a technique that expects a texture but don’t supply one you can send up with it reusing the texture from random other objects (it depends on the render order I’m pretty sure).

1 Like

That was it. Now that I think about it that makes sense.

Regenerated the XML to include an image and it works in tandem with the programmatic one. I will give it a dummy/loading image and stream the tile images in from the web.

Thank you.

Fun Fact
If I stream in an image I need to rotate it by 90° on the Y axis. If the image comes from disk no rotation required.

1 Like

Man, I wish there was a wiki or something. It would help a lot with the lack of documentation.

For anyone interested I got individual materials from the ResourceCache without the repeating problem (sorry to make you read through the entire thread but I am not re-typing all of that):

auto cache = GetSubsystem< Urho3D::ResourceCache >();

Urho3D::SharedPtr< Urho3D::Technique > tileTechnique;
tileTechnique = cache->GetResource< Urho3D::Technique >( "Techniques/Diff.xml" )->Clone();

const char* tileData = tileString.c_str();
Urho3D::MemoryBuffer tileMemoryBuffer( (void*)tileData, tileString.size() );
Urho3D::SharedPtr< Urho3D::Texture2D > tileTexture2D;
tileTexture2D = new Urho3D::Texture2D( context_ );
tileTexture2D->Load( tileMemoryBuffer );

Urho3D::SharedPtr< Urho3D::Node > tileNode_;
Urho3D::SharedPtr< Urho3D::StaticModel > tileObject;
Urho3D::SharedPtr< Urho3D::Material > tileMaterial;
tileNode_ = scene_->CreateChild( "tile" );
tileNode_->SetPosition( Urho3D::Vector3( 0, 4, 0 ) );
tileNode_->SetRotation( Urho3D::Quaternion( 0, 90, 0 ) );
tileNode_->SetScale( Urho3D::Vector3( MAP_TILE_MULTIPLIER, 1, MAP_TILE_MULTIPLIER ) );
tileObject = tileNode_->CreateComponent< Urho3D::StaticModel >();
tileObject->SetModel( cache->GetResource< Urho3D::Model >( "Models/Box.mdl" ) );
tileMaterial = cache->GetResource< Urho3D::Material >( "Materials/Tile.xml" )->Clone();
tileMaterial->SetTechnique( 0, tileTechnique );
tileMaterial->SetTexture( Urho3D::TU_DIFFUSE, tileTexture2D );
tileObject->SetMaterial( tileMaterial );

Note the Clone() on the material and the technique. I was driving myself crazy cloning one or the other but it turns out you need to clone both. Now you can assign arbitrary textures to materials without one changing the others or a ton of boilerplate code. Nor a need to generate a million (in my case much more than that) XML files for each tile. Plus this will save a lot of resource cache loading times.

Remember to keep an eye on the allocated memory. It would be very easy to have leaks all over the place.

:sparkles:

1 Like