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