Introduction

The Oculus Rift

The Oculus Rift is a virtual reality (VR) head-mounted display. By putting it on, you place yourself in a virtual world. To provide such an immersive experience, the Oculus Rift has several tracking sensors. They allow you to freely look around in the virtual world.

About the LuciadLightspeed support for Oculus

LuciadLightspeed allows you to create LuciadLightspeed applications for the Oculus Rift. It offers the following functionality:

  • Create an Oculus Rift-compatible Lightspeed view that behaves like a regular Lightspeed view with some minor limitations. The view is visualized on the Oculus Rift device and on a mirror view emulating the device display.

  • Handle the head tracking automatically so that the camera is always updated to look in the same direction as the user.

To this end, LuciadLightspeed introduces the com.luciad.oculus package, containing the necessary classes to create a view compatible with the Oculus Rift.

In addition to the API, LuciadLightspeed includes sample applications to show how to create an Oculus view and how to navigate in it. You can find those samples in the samples.lightspeed.oculus package.

About this guide

The purpose of this guide is to introduce the LuciadLightspeed Oculus package, and provide guidelines to developers for building an application with it. This guide is for developers who are familiar with Java and the LuciadLightspeed API.

The guide is organized as follows:

The functionality discussed in this guide is available in all LuciadLightspeed product tiers.

Getting started

This article describes the prerequisites you need to fulfill before you start using the LuciadLightspeed Oculus package. Next, it explains how you can create an Oculus Rift-compatible view with only a few lines of code. Developers who are already familiar with the LuciadLightspeed API will recognize many similarities with a regular Lightspeed view.

Prerequisites before you get started

Before you start using the LuciadLightspeed Oculus package, make sure that you:

  • Use high-end hardware. The Oculus Rift requires stereoscopic rendering, which means that two views are rendered, one for the left eye and one for the right eye. Rendering two views requires high-end hardware. It is advised to use a high-end dedicated GPU.

  • Update the GPU drivers to the latest version.

  • Download and install the Oculus Runtime software, which is currently available for Windows PCs only. You can download the Runtime from https://developer.oculus.com/.

  • Connect to the Oculus Rift device, and power it on.

  • Read the Oculus best practices and health guidelines on https://developer.oculus.com/documentation/ carefully.

Creating an Oculus view

To display a LuciadLightspeed application in the Oculus Rift, you need to create an Oculus view. An Oculus view implements the interface ILspView, so you can use it like any other view with some limitations. See Limitations for an overview.

In Program: Creating an Oculus view. we use the TLspViewBuilder to instantiate an Oculus view. We first have to create a TLspStereoscopicDevice that describes the Oculus device. You can create such an instance with the TLspOculusDeviceBuilder.

Some of the parameters you configure on the TLspViewBuilder will be ignored, because they serve no purpose for an Oculus view. The following parameters have no effect:

  • Size: we always render to the native resolution of the Oculus Rift.

  • View type: the Oculus Rift requires a 3D view to be used.

Program: Creating an Oculus view. (from samples/lightspeed/oculus/common/OculusSample)
private ILspView createOculusView() {
  TLspStereoscopicDevice oculusDevice = TLspOculusDeviceBuilder.newBuilder()
                                                               .build();
  return TLspViewBuilder.newBuilder()
                        .buildStereoscopicView(oculusDevice);
}

Application testing in the Oculus mirror view

To prevent that developers always have to wear the Oculus Rift device to test their applications, the LuciadLightspeed Oculus package offers a mirror view. In this mirror view, you see exactly what is being rendered to the Oculus Rift screen. The mirror view is useful for demonstration purposes as well.

The mirror view is enabled by default. You can disable it through the TLspOculusDeviceBuilder. The builder also allows you to configure the size of the mirror view.

The image in the mirror view looks slightly distorted because it applies a lens correction. The lens correction is required for rendering to the Oculus Rift. For more information, refer to the Oculus Rift documentation found on https://developer.oculus.com/documentation/.

mirrorView
Figure 1. Screenshot of Oculus mirror view

Creating controllers for the Oculus Rift

This article describes how you can create a controller to move around in an Oculus view.

An Oculus view does not live in a Java frame: it is rendered directly to the device. As a consequence, there is no mouse pointer available, and there are no mouse controls generating AWT events. That is why the existing LuciadLightspeed controllers will not work in an Oculus view.

Our suggested approach is to implement an ILspController that starts an ALcdAnimation when it is activated. In the animation you can listen to keyboard events and update the camera position according to the key that was pressed. You can update the camera position with TLspViewXYZWorldTransformation3D. From an Oculus view, you will always get a 3D view-to-world transformation, because it is the only supported transformation.

It is advised to use only TLspViewXYZWorldTransformation3D's lookFrom() method, as it is the most intuitive way to position the camera.

For more information about positioning the camera in a 3D view, see the LuciadLightspeed developer’s guide.

Program: Controller starts animation. (from samples/lightspeed/oculus/controller/OculusMoveController)
public OculusMoveController(ILcdHeightProvider aHeightProvider, double aHeightAboveTerrain, ILcdModel aPositionModel) {
  fHeightProvider = aHeightProvider;
  fHeightAboveTerrain = aHeightAboveTerrain;
  fPositionModel = aPositionModel;
}

@Override
public void startInteraction(ILspView aView) {
  super.startInteraction(aView);
  // Add an animation so that we can continually check if a key was pressed.
  TLspViewXYZWorldTransformation3D v2w = (TLspViewXYZWorldTransformation3D) aView.getViewXYZWorldTransformation();
  MoveAnimation moveAnimation = new MoveAnimation(v2w, fPositionModel, fHeightProvider, fHeightAboveTerrain);
  ALcdAnimationManager.getInstance().putAnimation(this, moveAnimation);
}

@Override
public void terminateInteraction(ILspView aView) {
  super.terminateInteraction(aView);
  ALcdAnimationManager.getInstance().removeAnimation(this);
}
Program: Move camera on key press. (from samples/lightspeed/oculus/controller/OculusMoveController)
@Override
protected void setTimeImpl(double aTime) {

  ILcdPoint eye = fV2w.getEyePoint();
  ILcdPoint reference = fV2w.getReferencePoint();

  // determine the direction vector the user is currently looking at.
  final TLcdXYZPoint newEyePoint = new TLcdXYZPoint(eye);
  Vector3d moveVector = new Vector3d((reference.getX() - eye.getX()),
                                     (reference.getY() - eye.getY()),
                                     (reference.getZ() - eye.getZ()));
  moveVector.normalize();

  int factor = 5;

  double distance = fV2w.getDistance();
  double yaw = fV2w.getYaw();
  double pitch = fV2w.getPitch();
  double roll = fV2w.getRoll();

  boolean apply = false;
  if (fKeyPressedChecker.isUpPressed()) {
    // move the eye point via the direction vector.
    newEyePoint.translate3D(moveVector.x * factor, moveVector.y * factor, moveVector.z * factor);
    apply = true;
  } else if (fKeyPressedChecker.isDownPressed()) {
    newEyePoint.translate3D(-moveVector.x * factor, -moveVector.y * factor, -moveVector.z * factor);
    apply = true;
  } else if (fKeyPressedChecker.isLeftPressed()) {
    yaw = yaw - 2;
    apply = true;
  } else if (fKeyPressedChecker.isRightPressed()) {
    yaw = yaw + 2;
    apply = true;
  }

  if (apply) {
    elevateEyePoint(fV2w.getView().getXYZWorldReference(), newEyePoint, fHeightAboveTerrain);
    fV2w.lookFrom(newEyePoint, distance, yaw, pitch, roll);
    ILcd3DEditablePoint position = (ILcd3DEditablePoint) fPositionModel.elements().nextElement();
    try (TLcdLockUtil.Lock autoUnlock = TLcdLockUtil.writeLock(fPositionModel)) {
      TLcdEllipsoid.DEFAULT.geoc2llhSFCT(newEyePoint, position);
      fPositionModel.elementChanged(position, ILcdModel.FIRE_LATER);
    } finally {
      fPositionModel.fireCollectedModelChanges();
    }
  }

}

Limitations

This article lists the limitations that currently apply to an Oculus view.

Not all features available for a regular LuciadLightspeed view can be used in an Oculus view. These are the unavailable features:

  • It is not possible to replace the view-to-world transformation of the view. This is done to enforce the use of a 3D view.

  • ALspCameraConstraints may interfere with the Oculus Rift’s head tracking, such as TLspAboveTerrainCameraConstraint3D). To let the camera follow a specific object, you can use the TLspLookFromTrackingCameraConstraint3D.

  • Because an Oculus view involves rendering two separate views, its frame rate will typically be lower than a regular LuciadLightspeed view. On the other hand, users of VR devices require a high frame rate for a fluid experience. Hence, to keep the performance as high as possible, it is recommended to only add the essential data to your view.

  • If you are using the TLspViewXYZWorldTransformation3D to position the camera, it is advised to use only the lookFrom() method. Using the lookAt() method may result in unexpected behavior.

  • Pre-existing LuciadLightspeed controllers will not function in an Oculus view.