Teleport Curves with the Gear VR Controller
Oculus Developer Blog
|
Posted by Gabor Szauer
|
November 1, 2017
|
Share

One of the most intuitive ways to implement locomotion in VR is teleportation. Teleportation is often implemented using some kind of a curve as the indicator for the teleportation path. One of the best examples of this type of locomotion system is Robo Recall. This post will refer to the type of teleportation system described above as arc teleportation, because of the visible arc of the curve used to visualize the path.

This post will explore the high level concepts needed to implement arc teleportation using three different techniques. Each technique is explained step-by-step and a Unity Package containing sample implementations for each is provided. The provided Unity Package contains scenes that can be navigated using each of the three methods. Tracking space is referenced throughout this post. Tracking space, in the context of this post refers to the transform of the TrackingSpace game object under OVRCameraRig. Most of the time the up direction of tracking space should be the same as the up direction of world space. The decision to use the up direction of tracking space was made to keep rays consistent even if the user moves to a surface that is at an odd angle.

Bézier curve

A static Bézier curve is the easiest method of implementing arc teleportation. This method creates a Bézier curve relative to the controller, then loops through every segment of the curve checking for a collision. Because every curve segment has to check for collision, it's a good idea to have a different number of visual segments and segments that are used to check for collision.

A quadratic Bézier curve consists of three points: a start point, an end point and a control point. Two line segments, S0 and S1 are created. S0 goes from the start point to the control point and S1 from the control point to the end point. Two new points, Q0 and Q1 are obtained by interpolating along S0 and S1 by some value t. t has a range between 0 and 1. Create another segment, S2 by connecting Q0 and Q1. Interpolate along this segment by t as well, the result of this interpolation is a new point O2. The interpolated point (Q2) on this segment is a point along the Bézier curve at t. The above formula can be expressed in unity with the following code:

```  Vector3 SampleCurve(Vector3 start, Vector3 end, Vector3 control, float t) {
// Interpolate along line S0: control - start;
Vector3 Q0 = Lerp(start, control, t);
// Interpolate along line S1: S1 = end - control;
Vector3 Q1 = Lerp(control, end, t);
// Interpolate along line S2: Q1 - Q0
Vector3 Q2 = Lerp(Q0, Q1, t)
return Q2; // Q2 is a point on the curve at time t
}
```

Using the this method of calculating a quadratic Bézier curve, one can be constructed for locomotion by following these steps:

1. Create a bezier curve from the controller
• The start point of the curve will be the position of the controller
• The end point of the curve will be the position of the controller plus some distance in the forward direction of the controller, plus some distance in the down direction of the controller.
• The control point is halfway between the start and end point, plus some distance in the up direction of the controller.
2. Loop trough each segment of the curve
• Perform a line test along the length of the curve segment
3. If any of the line tests hit a valid collider, register the hit point
4. If the line tests did not hit anything, use the end point of the curve as the hit point

The Bézier curve will translate and rotate with the controller. Changes in the pitch of the controller move the end point of the curve directly, the size of the curve is not affected by the controller. This means the end point of the curve can be above the controller. For an example of a game that implemented this locomotion design well, check out Cloudlands: VR Minigolf.

Parabolic curve

This curve works by tracing the parabola of a projectile from the controller. The force of the initial velocity and acceleration of the projectile can be configured in the inspector. Gravity is assumed to be in the down direction of tracking space, not world space. The direction of the initial velocity is the direction of the controller. The curve is plotted over time using the equation for parabolic motion.

`y = position + velocity * t + 1/2acceleration * t^2`

The curve is traced a little further than the point at which the it returns to the height of the controller. The distance at which the curve will return to the same height as the controller can be found with the following formula:

`velocity^2 * Sin (2 * angle) / acceleration;`

Some extra distance is added to the result of the above formula to allow the curve to drop below the horizon. This extra distance can be configured in the inspector. Performance wise, this is the most expensive of the three methods presented. However, it is also the most intuitive of the three.

The pitch of the controller changes the direction of the projectiles velocity and amount of time in the air. Because gravity is assumed to be in the down direction of tracking space and not the controller, this type of curve will always end below the controller.

Tall ray cast

Tall ray casting, as the name implies casts a ray from above the user. There is only a single ray cast as opposed to checking multiple line segments for intersection, making this the least computationally expensive method presented. This kind of ray cast can be visualized with any type of curve, we will use a Bézier curve for visualization.

Let's explore how this teleportation method works by assuming that the player is standing on a platform and wants to teleport to an adjacent platform. 1. Project the forward vector of the controller onto the X / Z plane of the tracking space. This will result in a ray pointing away from the player in the direction of the controller, let's call it Horizontal Ray.
2. ```Ray HorizontalRay {
get {
Vector3 horizontalDirection = Vector3.ProjectOnPlane (ControllerForward, Up);
Vector3 playerPosition = trackingSpace.position;
return new Ray (playerPosition, horizontalDirection);
}
}
``` 3. Find a point along the horizontal ray based on the pitch of the controller. Do this by normalizing the pitch range (moving it into a 0 to 1 range), then multiply it by the maximum distance the user should be able to cast along the horizontal ray.
4. ```public float HorizontalDistance {
get {
float controllerAngle = Vector3.Angle(Up * -1.0f, ControllerForward);
float pitch = Mathf.Clamp(controllerAngle, minControllerAngle, maxControllerAngle);
float pitchRange = maxControllerAngle - minControllerAngle;
float t = (pitch - minControllerAngle) / pitchRange; // Normalized pitch within range
return maxDistance * t;
}
}
Vector3 PointAlongHorizontalRay {
get {
return HorizontalRay.origin + HorizontalRay.direction * HorizontalDistance;
}
}
``` 5. Find the ray cast position, this will be some units above the player. Find the ray cast direction by subtracting the point we found along the horizontal ray in step 4 from the ray cast position and normalize the result. Knowing the position and direction, this ray will be cast into the world.
6. ```Vector3 CastPosition {
get {
Vector3 playerPosition = trackingSpace.position;
return playerPosition + Up * castHeight;
}
}
Ray CastRay {
get {
Vector3 castDirection = (PointAlongHorizontalRay - CastPosition).normalized;
return new Ray (CastPosition, castDirection);
}
}
``` 7. Perform a ray cast using the ray above the user. If anything was hit, use that as the end point. If nothing was hit, the end point will be along the horizontal ray.
8. 9. Render a Bézier curve between the controller and the end point. The Unity Package attached to this blog post contains code samples for all three teleportation systems. The implementation for each system contains more code than what is outlined in the post, for example the normal of the surface hit by the ray cast is checked to make sure the user can't teleport into a wall. Every component contains tooltips on public variables which explain how to set their values.

When the teleportation arc is over a valid location it turns fully opaque and a circular indicator appears on the ground. If the arc is not over a valid location, the circular indicator disappears and the arc becomes semi transparent. When the arc is over a valid location, touching the touchpad will cause a direction indicator to appear. Use the trigger to teleport. Touching the touchpad while pointing at a valid location will allow the target orientation after teleporting to be specified. The back button returns to the level select scene.

To use the provided samples, import Oculus Utilities for Unity into your project before importing the provided unity package. This sample was built against version 1.15.0 of Oculus Utilities. There are four sample scenes included in the attached Unity Project.:

• Start, this scene serves as a level select. This is the scene y
• BezierLocomotion, sample implementation of the Bézier curve method
• ParabolicLocomotion, sample implementation of the parabolic curve method
• TallLocomotion, sample implementation of the tall ray cast method

Teleporting works using three scripts, there is a “Raycaster”, a “Visualizer” and a “Teleporter”. All raycasters extend the `ArcRaycaster` base class, these scripts contain the logic for casting the arc ray out into the world and finding intersections. Similary, all visualizers derive the `ArcVisualizer` class, these scripts configure a line renderer to visualize the arc used to teleport. Finally, the `ArcTeleporter` script handles the actual logic of teleporting around in the world.

Using the demo in your project

The attached Unity Package contains several prefabs under the folder `ArcTeleporter/Prefabs.` The following three prefabs are configured locomotion systems configured with the same values that the demos use:

• BezierLocomotion
• ParabolicLocomotion
• TallLocomotion

To use one of these pre-configured locomotion systems in your own project, drag the appropriate prefab into a scene and set the `Object to Move` property of the ArcTeleporter component and the `Tracking space` property of the ArcRaycaster component. The height ArcTeleporter needs to be changed to the height of the camera as well, the default value is 1.29. 