Skip to content

Your First Janus Room

James Baicoianu edited this page Jan 14, 2022 · 2 revisions

Let's make a Janus room! It can be a little intimidating if you don't know where to begin, but you can get started with nothing more than a text editor. Many people's first question is "how do I build this project?" - while that is an option that advanced users might wish to take advantage of, it's not necessary, you can start building right away without ever having to check out the code.

The JanusXR Viewer

The first thing to know is that Janus rooms start off as just HTML - or more specifically, as an XML snippet embedded inside of an HTML page. The bare minimum usage of JanusXR looks like this:

<html>
  <body>
    <janus-viewer></janus-viewer>
    <script src="https://web.janusxr.org/janusweb.js"></script>
  </body>
</html>

You can host this HTML anywhere you would normally host a website - it's purely static HTML, so you don't need anything fancy - a traditional static web host will work, or you can use AWS, Digital Ocean, Squarespace, Wordpress, or any other host that lets you put raw HTML anywhere on the page. Sometimes people even host temporary pages on Pastebin or HackMD.

This code will give you the default client, pointing at our default landing page. You can go ahead and walk around, walk through portals, and check out some of the other rooms and worlds people have made.

Our Room

Once you're comfortable with how to get around, you'll probably want to start building your own! We can do that by adding some markup inside of our <janus-viewer> tag, like so:

<janus-viewer>
  <fireboxroom>
    <room use_local_asset="room1">
      <object id="cube" pos="0 .5 5" col="red" />
      <object id="sphere" pos="1 1 3" col="#ffff00" />
      <object id="cone" pos="-1 1 4" col="0 1 0" />
      <text pos="0 2 4">My First Janus Room</text>
    </room>
  </fireboxroom>
</janus-viewer>

This will give us a simple room using the "room1" template, with three primitives objects: a red cube, a yellow sphere, and a green cone. Notice how we can set colors with three different formats: using standard HTML color names, using #rrggbb (or the shorthand #rgb) format, or as "r g b", where each color component can be specified as a floating point value from 0..1. We've also got some 3d text floating above our objects.

Great! Now that we have this working, we can just keep building. Take a look at https://janusxr.org/docs/build/roomtag/index.html to see what types of built-in objects JanusXR supports - it's easy to add pictures, video, lights, sounds, links, and more to your room with the built-in object types. The <object id="..." /> parameter can take a number of built-in primitives like cone, sphere, cylinder, cube, pipe, plane, pyramid, torus, capsule, and cone.

Importing Models

We can also define our own custom geometry using 3d model files. A large number of legacy formats like OBJ, FBX, DAE (Collada), STL, PLY, and even VRML are supported, but our preferred format is binary glTF files, or GLB. To use these, we can add an <assets> section to our markup, like so:

<janus-viewer>
  <fireboxroom>
    <assets>
      <assetobject id="customroom" src="models/customroom.glb" />
      <assetobject id="monkey" src="models/monkey.glb" />
    </assets>
    <room>
      <object id="customroom" />
      <object id="cube" pos="0 .5 5" col="red" />
      <object id="sphere" pos="1 1 3" col="#ffff00" />
      <object id="cone" pos="-1 1 4" col="0 1 0" />
      <text pos="0 2 4">My Second Janus Room</text>
      <object id="monkey" pos="3 0 0" rotation="0 45 0" />
    </room>
  </fireboxroom>
</janus-viewer>

We've now replaced our simple built-in "room1" room with our custom room model, and we've added a monkey, rotated at 45 degrees on the Y axis. Check out https://sketchfab.com/ to find some models you might want to use, or you can build your own with https://blender.org/. The possibilities are endless here, you're limited only by your imagination - and by poly counts and material budgets, but that's a more in-depth topic :)

Interactive Scripting

Now that we've got custom models, we can start adding some custom scripting. JanusXR supports an advanced custom scripting system which allows you to define any type of behavior you might want for your specific application - game entities, UI elements, animated objects, and interactive systems. To do this, let's make a new file called scripts/my-custom-element.js, and we'll start off with this code:

room.registerElement('my-custom-element', {
  speed: 1,
  create() {
    this.createObject('object', {
      id: 'sphere',
      col: 'purple',
      scale: V(.1),
    });
  },
  update(dt) {
    this.pos.y = Math.sin(this.speed * Date.now() / 1000);
  }
});

Now in our markup, we can have the following:

<janus-viewer>
  <fireboxroom>
    <assets>
      <assetobject id="customroom" src="models/customroom.glb" />
      <assetobject id="monkey" src="models/monkey.glb" />
      <assetscript src="scripts/my-custom-element.js">
    </assets>
    <room>
      <object id="customroom" />
      <object id="cube" pos="0 .5 5" col="red" />
      <object id="sphere" pos="1 1 3" col="#ffff00" />
      <object id="cone" pos="-1 1 4" col="0 1 0" />
      <text pos="0 2 4">My Second Janus Room</text>
      <my-custom-element pos="-3 0 3" />
      <my-custom-element pos="3 0 0" speed="2">
        <object id="monkey" rotation="0 45 0" />
      </my-custom-element>
    </room>
  </fireboxroom>
</janus-viewer>

We're introducing a few new concepts here, so let's break it down. First, in our script, we registered a room element called <my-custom-element>. We defined an attribute for this element, speed, with a default value of 1. We also defined two functions for this object - create() and update(dt). All custom objects have these two functions by default - create() is called when the object is created, and can be used to initialize the object - we can spawn other objects inside of this element, fetch external resources, and create other scripting objects - say, an HTMLCanvasElement, or a Web Audio sound graph, which we'll use during the lifecycle of this object. Our update(dt) function will be called once every frame, and can be used to change the state of this object over time - in this case, we're bobbing up and down, at the speed we specified earlier.

Custom elements can also define their own functions beyond these two default functions, which allow you to define all manners of custom behaviors. For instance, maybe we want to have shrink() and grow() functions which change the scale of the object, or maybe we want our object to expose a fully-featured API for other objects to call - all doable, again, your only limits are your imagination and your own skills at architecting such systems!

Back in our markup, we've now added two new lines - an <assetscript> tag which tells our room to load our new script, and two instances of our <my-custom-element> tag. Our first one is nothing fancy, we just give it a position, and we should see a small purple sphere bouncing up and down once per second. Our second instance shows off some more advanced behavior - we can nest other objects inside of our element (and inside of each other, as well), and we also override the default speed. So here, we should see our small purple dot, and our monkey, bouncing up and down 2 times per second.

Clone this wiki locally