Incompatibility in how relative URLs are resolved, when used together with EXTERNPROTO - michaliskambi/x3d-tests GitHub Wiki
We have uncovered a serious incompatibility in handling of relative URLs when combined with EXTERNPROTO.
- Instant Player, Castle Model Viewer (and Castle Game Engine), X_ITE behave one way,
- while BS Contact and Cortona behave in another, different way.
The incompatibility only affects browsers that handle EXTERNPROTO, so testing X3DOM is not relevant here.
The issue (and what to do forward) is made even more difficult because, in my mind, both approaches are somewhat wrong, i.e. surprising and undesired in some (but different) scenarios.
Table of Contents:
- Testcases
- Instant Reality, Castle Model Viewer (and Castle Game Engine) and X_ITE approach
- BS Contact and Cortona approach
- A solution for X3D authors that works in all browsers intutively
- How should the specification look like?
Testcases
This repository contains a ready testcases for everything described below: https://github.com/michaliskambi/x3d-tests/tree/master/relative_urls_in_externproto .
We have also a much more elaborate testcase, exercising various edge-cases around this, inside the Castle Game Engine demo-models, subdirectory prototypes/relative_urls_test: https://github.com/castle-engine/demo-models/tree/master/prototypes/relative_urls_test . You probably don't want to look there immediately -- the testcase we created there got quite complicated :)
Instant Reality, Castle Model Viewer (and Castle Game Engine) and X_ITE approach
Instant Reality player, Castle Model Viewer and X_ITE resolve URLs with respect to the file that contains the node that contains this URL. Note that in some edge-cases (see below) this is not the same thing as "file that contains the URL".
This case is handled in a natural way:
- One X3D file
prototype.x3dvdefines aPROTOand refers to a texture with relative URL:#X3D V3.2 utf8 PROFILE Interchange PROTO MyGeometry [ ] { Shape { geometry Box { } appearance Appearance { texture ImageTexture { url "texture.png" } } } } - The file
prototype.x3dvand the accompanyingtexture.pngare in a subdirectory, liketextures/. - Now another X3D file, let us call it
parent_scene.x3dv, placed as a sibling of thetextures/directory (so, one level aboveprototype.x3dvandtexture.png) instantiates the prototype:#X3D V3.2 utf8 PROFILE Interchange EXTERNPROTO MyGeometry [ ] [ "textures/prototype.x3dv#MyGeometry" ] MyGeometry { }
The above works (in Instant Reality / Castle Model Viewer / X_ITE), and seems natural for the X3D author.
However, if the prototype exposes an MFString field to configure the texture URL, it is not longer intuitive. This is what works (in Instant Reality / Castle Model Viewer / X_ITE), but is not natural to the X3D author:
- One X3D file
prototype.x3dvdefines aPROTOand refers to a texture with a configrable URL:#X3D V3.2 utf8 PROFILE Interchange PROTO MyGeometry [ inputOutput MFString textureUrl "" ] { Shape { geometry Box { } appearance Appearance { texture ImageTexture { url IS textureUrl } } } } - Just like before, the file
prototype.x3dvand the accompanyingtexture.pngare in a subdirectory, liketextures/. - Now another X3D file,
parent_scene.x3dv, placed as a sibling of thetextures/directory (so, one level aboveprototype.x3dvandtexture.png) instantiates the prototype:#X3D V3.2 utf8 PROFILE Interchange EXTERNPROTO MyGeometry [ inputOutput MFString textureUrl ] [ "textures/prototype.x3dv#MyGeometry" ] MyGeometry { textureUrl "texture.png" }
What's unnatural above? parent_scene.x3dv refers to prototype.x3dv using a relative URL like this: textures/prototype.x3dv. However, it needs to refer to the texture using a relative URL like this: texture.png (without the textures/ subdirectory). That is because the URL is actually copied to the ImageTexture.url inside textures/prototype.x3dv, and it is resolved with respect to the textures/prototype.x3dv (not with respect to the parent_scene.x3dv).
So it works, but it's surprising for a normal user. It would be more natural to be able to write
MyGeometry { textureUrl "textures/texture.png" }
... but it doesn't work (in Instant Reality / Castle Model Viewer / X_ITE).
The problem is most dire when the prototype.x3dv is on a different server than the parent_scene.x3dv. Like, http://proto-definitions.org/prototype.x3dv and http://proto-instances.org/parent_scene.x3dv. In this case, parent_scene.x3dv has no way to pass a relative URL to a texture on it's own server (proto-instances.org). All relative URLs are resolved on server proto-definitions.org. So parent_scene.x3dv is forced to use absolute URLs.
BS Contact and Cortona approach
BS Contact and Cortona override the base URLs in expanded prototype (used to resolve relative URLs) to match the URL of the file with EXTERNPROTO declaration. This makes this case behave naturally:
textures/prototype.x3dvlooks like this:#X3D V3.2 utf8 PROFILE Interchange PROTO MyGeometry [ inputOutput MFString textureUrl "" ] { Shape { geometry Box { } appearance Appearance { texture ImageTexture { url IS textureUrl } } } }parent_scene.x3dv, looks like this:#X3D V3.2 utf8 PROFILE Interchange EXTERNPROTO MyGeometry [ inputOutput MFString textureUrl ] [ "textures/prototype.x3dv#MyGeometry" ] MyGeometry { textureUrl "textures/texture.png" }
Note that parent_scene.x3dv now, intuitively, can refer to resources using relative URLs versus it's own directory. So textures/prototype.x3dv or textures/texture.png.
However, on the other hand, the unnatural (and uncomfortable for authors) behavior arises when you try to use a relative URL inside a prototype definition. You need to write this:
textures/prototype.x3dvlooks like this:#X3D V3.2 utf8 PROFILE Interchange PROTO MyGeometry [ ] { Shape { geometry Box { } appearance Appearance { texture ImageTexture { url "textures/texture.png" } } } }parent_scene.x3dv, looks like this:#X3D V3.2 utf8 PROFILE Interchange EXTERNPROTO MyGeometry [ ] [ "textures/prototype.x3dv#MyGeometry" ] MyGeometry { }
What is weird above is that prototype.x3dv has to refer to the texture (that resides in the same directory as prototype.x3dv!) using a path textures/texture.png. Why? Because when the prototype is expanded, the URL will be resolved within parent_scene.x3dv.
So the PROTO author cannot reliably use relative URLs to refer to resources relative to the file that defines the prototype. If the PROTO author wants to use a texture on proto-definitions.org server, it cannot use relative URLs for this, even though the texture and prototype.x3dv are both on proto-definitions.org server. The URLs are resolved with respect to the file that instantiates this prototype, so each instance may refer to a different file (texture, inlined 3d model etc.).
We added in Castle Model Viewer a special new flag RebaseRelativeUrlsInProtos to make it (partially) compatible with BS Contact in this regard. By default, Castle Model Viewer (past and future) is compatible 100% with Instant Player and X_ITE in this regard.
See also https://github.com/michaliskambi/x3d-tests/issues/3 for tests on more X3D browsers.
A solution for X3D authors that works in all browsers intutively
How to specify a relative URL inside a parent_scene.x3dv in a way that works in both browsers?
One workaround is to provide alternatives relative URLs like this:
MyGeometry { textureUrl [ "textures/texture.png" "texture.png" ] }
The better solution, that works in all browsers and is intuitive for authors, is to never pass MFString as prototype parameter for URLs. Instead pass texture node as SFNode parameter. This works intuitively in all browsers. It looks like this:
- In
textures/prototype.x3dvdefines aPROTOwith a configrable texture node:#X3D V3.2 utf8 PROFILE Interchange PROTO MyGeometry [ inputOutput SFNode textureNode NULL ] { Shape { geometry Box { } appearance Appearance { texture IS textureNode } } } - In
parent_scene.x3dvyou can instantiate it like this:#X3D V3.2 utf8 PROFILE Interchange EXTERNPROTO MyGeometry [ inputOutput SFNode textureNode ] [ "textures/prototype.x3dv#MyGeometry" ] MyGeometry { textureNode ImageTexture { url "textures/texture.png" } }
This will work OK, because the PROTO parameter is now a complete node, and it's BaseUrl will behave in an intuitive way always: { url "textures/texture.png" } is relative to the file that instantiates the prototype, under both Instant Reality, Castle Model Viewer, BS Contact and Cortona.
How should the specification look like?
The X3D specification says this . It's not clear from this description which approach is right.
The perfect behavior (but this is something that noone implements, and specification doesn't allow it definitely) is to resolve URLs always with respect to the file in which the URL is written. But this requires storing base URL at each field (not only at node) to make all cases presented above OK. So it does make implementation more complicated, and possibly more memory-hungry (additional info per every field is a significant cost in some scenes). But it would make things always behave correctly.