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, view3dscene (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, view3dscene (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, view3dscene (and Castle Game Engine) and X_ITE approach
Instant Reality player, view3dscene 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.x3dv
defines aPROTO
and 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.x3dv
and the accompanyingtexture.png
are 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.x3dv
andtexture.png
) instantiates the prototype:#X3D V3.2 utf8 PROFILE Interchange EXTERNPROTO MyGeometry [ ] [ "textures/prototype.x3dv#MyGeometry" ] MyGeometry { }
The above works (in Instant Reality / view3dscene / 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 / view3dscene / X_ITE), but is not natural to the X3D author:
- One X3D file
prototype.x3dv
defines aPROTO
and 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.x3dv
and the accompanyingtexture.png
are in a subdirectory, liketextures/
. - Now another X3D file,
parent_scene.x3dv
, placed as a sibling of thetextures/
directory (so, one level aboveprototype.x3dv
andtexture.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 / view3dscene / 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.x3dv
looks 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.x3dv
looks 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 view3dscene a special new flag RebaseRelativeUrlsInProtos to make it (partially) compatible with BS Contact in this regard. By default, view3dscene (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.x3dv
defines aPROTO
with 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.x3dv
you 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, view3dscene, 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.