JME not decoding GLTF uri

So I ran into an issue where JME could not find an assets that was actually a texture dependency on a gltf model. This was because blender put a space in the name of the jpg when it created it during the export. GLTF encodes spaces as %20 per RFC3986. But the current jme GLTF plugin does not attempt to decode the encoded URI before using it, so it will pass the file name some%20name.jpg to the asset manager and it will be unable to find the file (AssetNotFoundException) which is actually some name.jpg.

Another interesting thing to take note, although I did not test this, GLTF does not restrict URIs to be files, they can also be a URL. I am not sure if this is handled yet, from my digging in the code it did not look like it.

I would think that doing Paths.get(url.toURI()).toFile() would be sufficient, but perhaps we need to check the protocol on the uri first, but gltf does not put the file:/ at the beginning of file URIs, so we would need to check for a http as the protocol, and assume file if not present. Since we have the UrlLocater I do not think it would be that difficult to make the http uri work.

Anyways, any thoughts on the process for this?

I am actually surprised that no one has run into this issue yet, although I just hit it myself yesterday after working with many gltf models that last year.

If I have some time today I might see if I can get something working.

For more info on the uri encoding in gltf, see here: Whitespace in URIs Ā· Issue #1449 Ā· KhronosGroup/glTF Ā· GitHub

Thanks,
Trevor

Maybe too many people have been burned with weird ā€œspaces in file namesā€ problems and just gave up ever using spaces years ago and just don’t think about it anymore.

…or maybe that’s just me.

When I see people put spaces in file names, to me it’s like watching those folks that stand too close to the edge of the platform when the train is coming in.

1 Like

In this case, the files with spaces are auto generated by blender. When exporting a FBX with materials to GLTF, blender will create a bunch of ā€œmapā€ texture files, and they all have spaces in the name.

Is there a specific reason they would do that that you can think of? Just curious why they would do that.

Not sure, this is what blender exports:

which in the gltf gets encoded as "uri" : "Map%20001-1.jpg"

These are the original textures that the FBX uses:

I am building a quick test program for doing the decoding, and because they do not follow the RFC and fail to put the file:/// protocol designator, it throws an error with the URI class. After some googling, I also discovered that older exporters do not perform the encoding on the uri, and will just leave spaces, which of course also throws an error in javas URI class.

I am coming up with some very hacky ways around this, like checking for a space and then not decoding, and if it does not start with http: or https:, then I add the file:///… But I do not like needing a hacky work around like this. I feel like there should be a better way. Or that khronos group neesds to get their act together and get the gltf standard actually standardized.

I am open to suggestions on performing the uri decoding. Because it needs to handle non-encoded characters that should be encoded, and it needs to decode regardless of a protocol specified or not, it makes this difficult without writing a custom decoded. Any ideas?

It’s been a while, but there should already be in Java a way to decode/encode ā€œURI safeā€ strings without having to create a proper URI. It would happily take spaces or %20 and end up with spaces.

You haven’t actually shown what it looks like in the gltf so I can’t comment further and don’t have time to try it myself right now.

For the sake of not posting a giant model, I will include a snippet. Is there a specific piece of the model you would like to see? Here are the uri references.

"images" : [
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-1.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-2.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-3.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-1.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-4.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-5.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-2.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-6.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-3.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-7.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-8.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-4.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-9.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-10.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-5.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-11.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-12.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-6.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-13.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-14.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-7.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-15.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-16.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-8.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-17.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-18.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-9.png"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-19.jpg"
       },
       {
           "mimeType" : "image/jpeg",
           "name" : "Map #001",
           "uri" : "Map%20001-20.jpg"
       },
       {
           "mimeType" : "image/png",
           "name" : "Map #001-Map #001",
           "uri" : "Map%20001-Map%20001-10.png"
       }
   ],

The URLDecoder I believe will perform the decoding, but it also has issues, like decoding + to a space, which is a completely valid filesystem character.

…or maybe that’s just me.

hehe, same, always try avoid spaces in ANY file names(just to be sure) :slight_smile:

But it means something specific in a ā€œURIā€ā€¦ which is what the field is labeled.

We aren’t trying to decode a ā€œfileā€ field. We’re trying to decode a ā€œuriā€ field.

At the end of the day, there’s only so much we can do, I guess.

With some testing I think this will work, but I am sure that some edge case will come up where it does not…
URLDecoder.decode(uri.replace("+", "%2B"), "UTF-8")

EDIT:
I have created a branch where I am testing this.

Yeah, it will work until the + is supposed to mean space like a proper URI and then this will break.

Do you have examples where they used + in a URI and actually meant +? Or are you just presuming they haven’t handled that case properly?

The only case would be in a terrible file name, such as packed+alpha.png, which is a legit file name god forbid someone actually name something like that.

In this case, until we support web uri in the gltf, I do not think we have to worry about a legit query string with a +, and when that comes up, there will need to be a bit of rework on the gltfloader.

I’m fine either way (to have it or not) on the fix for the +, I would not anticipate a file with a plus in it, but I would not have anticipated blender making spaces in file names either.

I have opened a PR, feel free to comment on it:

Yeah, the thing is that blender is not the only gltf producer… just our most common one. Some other exporter may choose to use proper URIs and like + better than %20 for readability or something.

Hmmm… I’m not sure then. I guess I can remove the + work around, and see if someone has issues with it later. Either way it will cause issues for one scenario or another. I can see both sides. Really if they are going to be using proper encoding, they should only use the plus for a space.

I guess for now I will remove it.

Yes, that’s my point. We don’t really know what bugs some exporter will have so when in doubt ā€œbe correctā€. And ā€œcorrectā€ in this case is to let the + convert to a space.

If we have to add additional work-arounds in the future then we can… hopefully we don’t end up with a lot of ā€œif vendor = foo and version > x then correct thisā€ style code. (I don’t even remember but I assume gltf has exporter information like that.)

1 Like