Skip to content

open nested rooms#309

Open
coderofsalvation wants to merge 12 commits into
jbaicoianu:masterfrom
coderofsalvation:feat/overlayrooms
Open

open nested rooms#309
coderofsalvation wants to merge 12 commits into
jbaicoianu:masterfrom
coderofsalvation:feat/overlayrooms

Conversation

@coderofsalvation
Copy link
Copy Markdown
Contributor

@coderofsalvation coderofsalvation commented Apr 13, 2026

demo: import portal room

source: <link overlay="true" url="https://xrforge.isvery.ninja"/>

video-22-cut-merged-1775758896357-ezgif.com-gif-maker.mp4

demo: recursive room

source: overlay_recursive.xml

video-26-ezgif.com-gif-maker.mp4

this PR enables opening nested room via

<link url="...." overlay="true"/>   
<!-- overlay="true" calls `janus.merge(url)` instead of `janus.load(url)` -->

I've played a bit around, with different implementations.
The issue I stumbled upon is that

  • scripts are running either in the nested room or parent room (strategy A)
  • if a user opens a nested room, that user suddenly dissappears for other users (strategie A)
  • some things should be disabled when opening a nested room (xrfragment sidecarfiles, skybox, fog etc)
  • nested room needs to be repositioned/reoriented to the location of the <link> portal

Testing matrix

strategy assetscripts parent assetscripts nested comms url changes? strength
A. setActiveRoom(nested) nested room to nested room collab editing
B. skip setActiveRoom(nested) parent no discovery-map gaming
C. parseAndAddObjects(nestedroom) parent no discovery-map gaming

I've got the best results (and least amount of code) with B.
The collaborational editing feature needs a different brainstorm I think.

Any thoughs welcome, but here's something which works and is tested (including the back-button, the nested open rooms persists when navigating back):

https://github.com/coderofsalvation/janusweb/blob/feat/overlayrooms/tests/room/overlay.xml

@coderofsalvation coderofsalvation changed the title wip: open nested rooms (dont merge) open nested rooms Apr 13, 2026
@jbaicoianu
Copy link
Copy Markdown
Owner

Yeah, janus.setActiveRoom() as implemented assumes that only one room will be active at once. As it is now it will call janus.clear() which removes the "current" room and calls currentroom.disable() which deregisters all of the event handlers which drive the scripting engine.

I see in your branch you've implemented a new function, janus.merge() and patched janus.load() to load the merged room and add it as a child of the existing room. This does a good job of grouping the subrooms together, although it somewhat confuses the isolation between them. It might be a cleaner implementation to add the room as a child of the janus objects like we do with the current "active" room, but then we would have to manually implement clean-up and other more advanced room management, which means tracking which merged rooms are associated with which "main" room.

Another potential gotcha with adding rooms as children of rooms is that the room element does not inherit from the janusbase element, which leads to some subtle differences in API. When room.enable() or room.disable() are called, we call object.start() / object.stop() for all children which define those functions. However, the room element instead implements room.enable() and room.disable() and there is no room.start() or room.stop() which I believe means that your merged rooms would continue running. We could patch room to have start()/stop() functions aliased to enable()/disable() which should solve this.

It's not immediately clear to me how rooms merged this way will interact with the editor and the network manager. In theory it's possible for each merged room to exist as a separate networking context, and the editor could be extended to understand the idea that multiple contexts may be active, and objects need to belong to a specific one, and not always the main room context.

I'm a bit confused about some of the stuff in the xrfragments translator. Looks like we're monkeypatching janus.setActiveRoom() here, I guess something with nested rooms is causing a non-string object to be passed? Does it make sense to merge some of these changes (like audio fade-out) into the main janus.setActiveRoom() function?

@coderofsalvation
Copy link
Copy Markdown
Contributor Author

coderofsalvation commented Apr 15, 2026

I'm a bit confused about some of the stuff in the xrfragments translator. Looks like we're monkeypatching
janus.setActiveRoom() here, I guess something with nested rooms is causing a non-string object to be passed? Does it
make sense to merge some of these changes (like audio fade-out) into the main janus.setActiveRoom() function?

We can, I wasn't sure that's why I first put into a patch.
If it's not an intended effect, I'd be happy to patch janus.setActiveRoom directly.

It might be a cleaner implementation to add the room as a child of the janus objects like we do with the current "active" room, but then we would have to manually implement clean-up and other more advanced room management, which means tracking which merged rooms are associated with which "main" room.

In the current version, I ended up duplicating the room via this line.
Please see the recursive movie (scroll to top in this GH issue), I think it's quite cool.
I'm making the assumption here that already loaded assets will not be loaded again.
This will spawn/add a new room-object to the janus.rooms-array, which will be added as a janus-child to the portal which triggered it (in order to orient/position the room appropriately).

The current implementation is kind of the best of the worst, since both (not) calling setActiveRoom() have their own side-effects.
The only remaining side-effect (or feature) is that when player A walks into an (opened) nested room/portal,

then player B will see player A walking into an empty space.
From a performance perspective this is good (perhaps player A should dissappear?), otherwise the increase of players would tank the performance.

@coderofsalvation coderofsalvation changed the title open nested rooms open nested rooms (wip: dont merge yet!) Apr 17, 2026
@coderofsalvation
Copy link
Copy Markdown
Contributor Author

Here some more (stress)tests:

Cross-server world (`auto_load="true")

Compose one world out of multiple worlds:

    <link url_pos="0 0 0" url="https://vesta.janusxr.org/spyduck/new-bark-town-pokemon-gsc" auto_load="true" ...
    <link url_pos="0 0 0" url="https://vesta.janusxr.org" auto_load="true" auto_load_delay="3000" ...
    <link url_pos="0 0 0" url="https://www.janusxr.org/newlobby/index.html" auto_load="true" ...
    <link url_pos="0 0 0" url="https://xrforge.isvery.ninja/models/zzswz4qlqw8w/model_files/test.xrf.glb" ...

TBH. I have not seen any solution which allows for composing such a cross-server world:

video-28-cut-merged-1776355259830-ezgif.com-gif-maker.mp4

ps. another win: it made me discover this bug.

#! XR Fragment operator + target="...."

Following the XR Fragment spec these URLs can indicate object imports / parent-toggling.

<link url="foo.xml#!" ...>      <!-- imports foo.xml as child of the link/portal -->
<link url="foo.xml#!p2" ../>   <!-- import object 'p2' from foo.xml as child of link/portal -->
<link url="foo.xml#!p2" target="cube"/>  <!-- same but attach as child to cube -->
<link url="foo.xml#!p2" target="player"/>  <!-- same but attach as child to player -->
video-29-ezgif.com-gif-maker.mp4

@coderofsalvation
Copy link
Copy Markdown
Contributor Author

coderofsalvation commented Apr 17, 2026

It might be a cleaner implementation to add the room as a child of the janus objects

Exactly what I ended up doing..the portal janusobject itself is the default parent of a nested room (and can be can be changed via `target='anotherobject')

there is no room.start() or room.stop() which I believe means that your merged rooms would continue running.

Thanks for noticing, I've introduced room.getObjectsDeep()

    this.enable = function() {
      this.getObjectsDeep()
          .filter( (obj) => obj.start   )
          .map(    (obj) => obj.start() )

It allowed me to patch room.enable() and room.disable() to call start() and stop() on objects in nested rooms too (here is the commit)

@coderofsalvation coderofsalvation changed the title open nested rooms (wip: dont merge yet!) open nested rooms #ready-for-merge May 14, 2026
@coderofsalvation
Copy link
Copy Markdown
Contributor Author

coderofsalvation commented May 14, 2026

image

OK I think this PR is ready.
Thanks to ✾ a fix against clicks bubbling up to parent rooms has been adressed.
I've added an extra aframe.js translator which allows autoconversion/embedding/viewing AFRAME geometry as Janus rooms.
I've also added AFRAME-URLs to tests/room/overlay_worlds.xml as a litmus test (see screenshot).
The AFRAME translator is limited to primitive-tags & 3D files (<a-box> <a-cylinder> <a-gltf-model> e.g.) + <a-entity> versions of those..but assetscripts can add further support things on a per-AFRAME-URL-basis via XML-parsing-events:

 room.addEventListener('aframe_element', (e) => {
   const {scene,el,translator} = e.data 
   // el = <a-entity material="color:#FFF; sides: front"/>
   // let attrs = translator.parse( el.getAttribute("material") ) // {color:#FFF}
 })

Usecases: AFRAME URLs as 3D thumbnails, embedding content from AFRAME experiences.

Also I've updated the README.md with source/demo-links to it, which will work once this PR is merged.

@coderofsalvation coderofsalvation changed the title open nested rooms #ready-for-merge open nested rooms May 14, 2026
@coderofsalvation
Copy link
Copy Markdown
Contributor Author

please merge #316 instead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants