Skip to content
This repository has been archived by the owner on Dec 15, 2020. It is now read-only.

Commit

Permalink
Add touch panning on mobile.
Browse files Browse the repository at this point in the history
Summary: Enabled by default. To turn off, set disableTouchPanning in VRInstance.

Reviewed By: andrewimm

Differential Revision: D5011760

fbshipit-source-id: 680314a
  • Loading branch information
amberroy authored and facebook-github-bot committed May 8, 2017
1 parent eb0ba7a commit fc01746
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 4 deletions.
8 changes: 6 additions & 2 deletions OVRUI/src/Control/AppControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import VRControls from './VRControls';

import type {Camera} from 'three';

export type AppControlsOptions = {
disableTouchPanning?: boolean,
};

export interface Controls {
camera: Camera,

Expand All @@ -31,10 +35,10 @@ export default class AppControls {
vrControls: ?VRControls;
nonVRControls: Controls;

constructor(camera: Camera, target: Element | void) {
constructor(camera: Camera, target: Element | void, options: AppControlsOptions = {}) {
this._camera = camera;
this.nonVRControls = DeviceOrientationControls.isSupported()
? new DeviceOrientationControls(camera)
? new DeviceOrientationControls(camera, target, options)
: new MousePanControls(camera, target);
}

Expand Down
22 changes: 21 additions & 1 deletion OVRUI/src/Control/DeviceOrientationControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import THREE from '../ThreeShim';
import MobilePanControls from './MobilePanControls';

import type {Camera} from 'three';

Expand All @@ -31,6 +32,10 @@ type DeviceOrientationEvent = {
absolute: boolean,
};

type DeviceOrientationControlsOptions = {
disableTouchPanning?: boolean,
};

// Unit vectors
const Y_UNIT = new THREE.Vector3(0, 1, 0);
const Z_UNIT = new THREE.Vector3(0, 0, 1);
Expand All @@ -52,12 +57,23 @@ export default class DeviceOrientationControls {
camera: Camera;
deviceOrientation: DeviceOrientation;
enabled: boolean;
mobilePanControls: MobilePanControls;
screenOrientation: number;
_initialAlpha: number | null;

constructor(camera: Camera) {
constructor(
camera: Camera,
target: Element | void,
options: DeviceOrientationControlsOptions = {}
) {
this.camera = camera;
this.enabled = true;
this.mobilePanControls = new MobilePanControls(camera, target);

// Allow touch panning unless explicitly disabled.
if (!!options.disableTouchPanning) {
this.mobilePanControls.enabled = false;
}

// Screen orientation (potrait, landscape, etc.), in radians
this.screenOrientation = getScreenOrientation();
Expand Down Expand Up @@ -135,5 +151,9 @@ export default class DeviceOrientationControls {
quaternion.multiply(SCREEN_ROTATION); // rotate from device top to a screen normal
rotation.setFromAxisAngle(Z_UNIT, -orient);
quaternion.multiply(rotation); // Account for system-level screen rotation

if (this.mobilePanControls.enabled) {
this.mobilePanControls.update();
}
}
}
110 changes: 110 additions & 0 deletions OVRUI/src/Control/MobilePanControls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

import THREE from '../ThreeShim';

// Unit vector
const Y_UNIT = new THREE.Vector3(0, 1, 0);

// Preallocated Quaternion to use each frame.
const rotation = new THREE.Quaternion();

// Emperically determined multiplier
const PAN_SPEED = 0.5;

/**
* MobilePanControls allows manipulation of the camera with touch on mobile.
*/
export default class MobilePanControls {
/**
* Create a MobilePanControls instance, and attaches the necessary event
* listeners
* @param camera - A Three.js Camera to control
* @param target - An optional DOM element to attach the mouse events to.
* Defaults to the `window` object.
*/
constructor(camera, target) {
this._camera = camera;
this._target = target || window;

this.enabled = true;

this._panStart = new THREE.Vector2();
this._panEnd = new THREE.Vector2();
this._panDelta = new THREE.Vector2();
this._theta = 0;
this._tracking = false;

// Ensure that event handlers are bound to this object
this._downHandler = this._downHandler.bind(this);
this._moveHandler = this._moveHandler.bind(this);
this._upHandler = this._upHandler.bind(this);

this.connect();
}

connect() {
this._target.addEventListener('touchstart', this._downHandler);
window.addEventListener('touchmove', this._moveHandler);
window.addEventListener('touchend', this._upHandler);
this.enabled = true;

// Should start untracked.
this._tracking = false;
}

disconnect() {
this._target.removeEventListener('touchstart', this._downHandler);
window.removeEventListener('touchmove', this._moveHandler);
window.removeEventListener('touchend', this._upHandler);
this.enabled = false;
}

_downHandler(e) {
// Ignore if multiple touches
if (e.touches.length !== 1) {
return;
}
const touch = e.touches[0];
this._panStart.set(touch.pageX, touch.pageY);
this._tracking = true;
}

_upHandler() {
this._tracking = false;
}

_moveHandler(e) {
if (!this._tracking) {
return;
}

const touch = e.touches[0];
this._panEnd.set(touch.pageX, touch.pageY);
this._panDelta.subVectors(this._panEnd, this._panStart);
this._panStart.copy(this._panEnd);

// Invert rotation so we pan in correct direction
this._panDelta.x *= -1;

const element = document.body;
this._theta += 2 * Math.PI * this._panDelta.x / element.clientWidth * PAN_SPEED;
}

update() {
if (!this.enabled) {
return;
}

// Update the camera rotation quaternion
const quaternion = this._camera.quaternion;
rotation.setFromAxisAngle(Y_UNIT, -this._theta);
quaternion.premultiply(rotation);
}
}
9 changes: 8 additions & 1 deletion OVRUI/src/Player/Player.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import setStyles from './setStyles';
import VREffect from '../Control/VREffect';

import type {Camera, Scene, Vector3, WebGLRenderer} from 'three';
import type {AppControlsOptions} from '../Control/AppControls';

type PlayerOptions = {
allowCarmelDeeplink?: boolean,
antialias?: boolean,
calculateVerticalFOV?: (number, number) => number,
camera?: Camera,
canvasAlpha?: boolean,
disableTouchPanning?: boolean,
elementOrId?: string | Element,
height?: number,
hideFullscreen?: boolean,
Expand Down Expand Up @@ -136,6 +138,7 @@ function isMobileInLandscapeOrientation() {
export default class Player {
allowCarmelDeeplink: boolean;
calculateVerticalFOV: ?(number, number) => number;
controlOptions: AppControlsOptions;
controls: AppControls;
effect: ?VREffect;
fixedPixelRatio: boolean;
Expand All @@ -162,6 +165,7 @@ export default class Player {
* and camera controls, and attach them to the specified place in the DOM.
* Creates a default camera if none is provided.
* @param options - An optional set of configuration values:
* disableTouchPanning: disable touch to pan camera on mobile. Defaults to false.
* elementOrId: the DOM location to mount the player. Can either be a DOM
* node, or the string id of a DOM node. Defaults to document.body
* camera: A Three.js Camera, to be used with the 3d renderer.
Expand All @@ -183,6 +187,9 @@ export default class Player {
this.allowCarmelDeeplink = !!options.allowCarmelDeeplink && isSamsung;
this.calculateVerticalFOV = options.calculateVerticalFOV;

// Options passed to AppControls contructor.
this.controlOptions = {disableTouchPanning: !!options.disableTouchPanning};

let width = options.width;
let height = options.height;
let pixelRatio = options.pixelRatio;
Expand Down Expand Up @@ -289,7 +296,7 @@ export default class Player {
renderer.setSize(width, height);
renderer.setClearColor(0x000000);
this.glRenderer = renderer;
this.controls = new AppControls(this._camera, this.glRenderer.domElement);
this.controls = new AppControls(this._camera, this.glRenderer.domElement, this.controlOptions);
this.onEnterVR = options.onEnterVR;
this.onExitVR = options.onExitVR;

Expand Down
3 changes: 3 additions & 0 deletions ReactVR/js/VRInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {Camera} from 'three';

type VRInstanceOptions = {
allowCarmelDeeplink?: boolean,
disableTouchPanning?: boolean,
assetRoot?: string,
camera?: Camera,
cursorVisibility?: 'visible' | 'hidden' | 'auto',
Expand Down Expand Up @@ -61,6 +62,7 @@ export default class VRInstance {
* @param options (optional) - Extra options to configure the VRInstance.
* - camera: the Three.js camera. If none, a default camera is created.
* - cursorVisibility: sets when the cursor is shown. default=hidden
* - disableTouchPanning: disallow touch to pan camera on mobile. Defaults to false.
* - height: a number specifying the height of the VR window, in pixels
* - nativeModules: array of native module instances to register
* - scene: the Three.js scene to which ReactVR elements are added
Expand Down Expand Up @@ -100,6 +102,7 @@ export default class VRInstance {
onEnterVR: () => this._onEnterVR(),
onExitVR: () => this._onExitVR(),
allowCarmelDeeplink: allowCarmelDeeplink,
disableTouchPanning: options.disableTouchPanning,
});

let defaultAssetRoot = 'static_assets/';
Expand Down

0 comments on commit fc01746

Please sign in to comment.