Babylon JS Day 14

Today I learned how to add VR support to my scene. Until now I’ve been using a template that Babylon JS provided in their Getting Started guide. I had to replace most of that code with an async version to be able to use the WebXR features that Babylon JS offers.

// Add scene code here
const createScene = () => {
  // Scene, camera, lights
  const scene = new BABYLON.Scene(engine);
  scene.debugLayer.show();
  scene.clearColor = BABYLON.Color3.FromHexString("#35013f");

  const ambientLight = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 10, 0));
  ambientLight.position = new BABYLON.Vector3(-1, 20, 30);
  const directionalLight = new BABYLON.DirectionalLight("DirectionalLight", new BABYLON.Vector3(0, -1, -3), scene);
  directionalLight.position = new BABYLON.Vector3(-1, 20, 30);

  const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 3, new BABYLON.Vector3(0, 0, 0));
  camera.upperBetaLimit = Math.PI / 2.2;
  camera.lowerRadiusLimit = 1;
  camera.upperRadiusLimit = 50;
  camera.setPosition(new BABYLON.Vector3(0, 0, -20));
  camera.attachControl(canvas, true);

  // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
  var ground = BABYLON.Mesh.CreateGround("ground1", 20, 20, 2, scene);
  var groundMaterial = new BABYLON.StandardMaterial(scene);
  groundMaterial.diffuseColor = new BABYLON.Color3.FromHexString("#9ba8b8");
  ground.material = groundMaterial;

  return scene;
};

// Call the scene
const scene = createScene(); //Call the createScene function

//Register a render loop to repeatedly render the scene
engine.runRenderLoop(function () {
  scene.render();
});
// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
  engine.resize();
});
window.addEventListener("DOMContentLoaded", async function () {
  // get the canvas DOM element
  var canvas = document.getElementById("renderCanvas");
  // load the 3D engine
  var engine = new BABYLON.Engine(canvas, true);
  // createScene function that creates and return the scene
  var createScene = async function () {
    // create a basic BJS Scene object
    var scene = new BABYLON.Scene(engine);
    scene.debugLayer.show();
    // create a FreeCamera, and set its position to (x:0, y:5, z:-10)
    var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 1.5, -5), scene);

    // target the camera to scene origin
    camera.setTarget(new BABYLON.Vector3(2, 1.5, 0));
    // attach the camera to the canvas
    camera.attachControl(canvas, false);
    // create a basic light, aiming 0,1,0 - meaning, to the sky
    var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);

    // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
    var ground = BABYLON.Mesh.CreateGround("ground1", 20, 20, 2, scene);
    var material = new BABYLON.StandardMaterial(scene);
    material.alpha = 1;
    material.diffuseColor = new BABYLON.Color3.FromHexString("#9ba8b8");
    // material.diffuseColor = new BABYLON.Color3(1.0, 0.2, 0.7);
    ground.material = material;



    // Add XR support
    // Enable XR
    var experience = await scene.createDefaultXRExperienceAsync({
      // define the floor meshes
      floorMeshes: [ground]
    });

    // return the created scene
    return scene;
  };

  // call the createScene function
  var scene = await createScene();

  // run the render loop
  engine.runRenderLoop(function () {
    scene.render();
  });

  // the canvas/window resize event handler
  window.addEventListener("resize", function () {
    engine.resize();
  });
});

I’m using the createDefaultXRExperienceAsync helper. Over time I need to learn how to customize the VR features a bit, but for now I’m going to use all their default settings.

Once I got the scene loaded in VR on my Oculus Quest 2, the first obvious change that I needed to make was scaling the cards down in size. When I built the card earlier this week, I sized them with “full unit” values to keep things simple. While that looks OK on the desktop browser, in VR these cards were huge. It was just a matter of scaling the entire card (with all child objects) at the end of my createCard function.

card.scaling = new BABYLON.Vector3(0.15, 0.15, 0.15);

Then I turned my attention to the event for the buttons on the cards. When a user presses this button, I want to show an expanded detail UI on another object in the scene. The scene will only have one “detail card” but may contain any number of the smaller “info cards”. Think of this as a master-detail interface, but in 3D space. First, I added a placeholder object for the detail card. I made this much larger than the item cards and positioned it behind them for now. Assigning a name in the constructor is important. I’ll need that to query this item in the scene graph.

const detailCard = BABYLON.MeshBuilder.CreateBox("detailCard", { height: 4, width: 6, depth: 0.4 });
    detailCard.position = new BABYLON.Vector3(5, 4, 6);

Then I updated the button event. I used a method on the scene called getMeshByName() to get a reference to the detail card in the scene, then toggle the isEnabled property on the detail card. Later, this button will also send data related to the info card to the detail card for display. While the info card contains a few preview items, the detail card will contain full text, images, etc.

button1.onPointerUpObservable.add(function () {
    let detailCard = scene.getMeshByName("detailCard");
    detailCard.setEnabled(detailCard.isEnabled() ? false : true);
    console.log("card button was clicked");
  });

Tomorrow I’ll get to work connecting all these features together. I want the user to be able to grab and move the info cards. I’d like to replace the button the card with a controller interaction. For example, say the user picks up a card and presses the A button to show the detail card. I also want to the detail card to show up in a position relative to the users’ position, not a fixed spot like it is now. I’m not sure how much I’ll be able to get done during the time I’ve allotted for the Friday Project, but I’m sure I’ll have something interesting to write about when I’m done.

Here are a couple of rough previews of the info and detail cards, recorded in the Oculus Quest 2.

A demo of the Info Cards in VR
A demo of the Info Cards toggling the Detail Card