Skip to content

Commit

Permalink
fix: re-implement touch pan controls (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonocasey committed May 23, 2018
1 parent e58862d commit 0cde016
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 13 deletions.
19 changes: 17 additions & 2 deletions scripts/rollup-replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import replace from 'rollup-plugin-re';
const modules = [
'VRControls',
'VREffect',
'OrbitControls'
'OrbitControls',
'DeviceOrientationControls'
];

const globalReplace = function(str, pattern, replacement) {
return str.replace(new RegExp(pattern, 'g'), replacement);
};

export default function(options) {
return replace(Object.assign({
include: ['node_modules/three/examples/js/**'],
Expand All @@ -21,10 +26,20 @@ export default function(options) {
code = code.replace(`THREE.${m} =`, `import * as THREE from 'three';\nvar ${m} =`);

// change references from the global modification to the local variable
code = code.replace(new RegExp(`THREE.${m}`, 'g'), m);
code = globalReplace(code, `THREE.${m}`, m);

// export that local variable as default from this module
code += `\nexport default ${m};`;

// expose private functions so that users can manually use controls
// and we can add orientation controls
if (m === 'OrbitControls') {
code = globalReplace(code, 'function rotateLeft\\(', 'rotateLeft = function(');
code = globalReplace(code, 'function rotateUp\\(', 'rotateUp = function(');

code = globalReplace(code, 'rotateLeft', 'scope.rotateLeft');
code = globalReplace(code, 'rotateUp', 'scope.rotateUp');
}
});
return code;
}}
Expand Down
97 changes: 97 additions & 0 deletions src/orbit-orientation-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as THREE from 'three';
import OrbitControls from 'three/examples/js/controls/OrbitControls.js';
import DeviceOrientationControls from 'three/examples/js/controls/DeviceOrientationControls.js';

/**
* Convert a quaternion to an angle
*
* Taken from https://stackoverflow.com/a/35448946
* Thanks P. Ellul
*/
function Quat2Angle(x, y, z, w) {
const test = x * y + z * w;

// singularity at north pole
if (test > 0.499) {
const yaw = 2 * Math.atan2(x, w);
const pitch = Math.PI / 2;
const roll = 0;

return new THREE.Vector3(pitch, roll, yaw);
}

// singularity at south pole
if (test < -0.499) {
const yaw = -2 * Math.atan2(x, w);
const pitch = -Math.PI / 2;
const roll = 0;

return new THREE.Vector3(pitch, roll, yaw);
}

const sqx = x * x;
const sqy = y * y;
const sqz = z * z;
const yaw = Math.atan2(2 * y * w - 2 * x * z, 1 - 2 * sqy - 2 * sqz);
const pitch = Math.asin(2 * test);
const roll = Math.atan2(2 * x * w - 2 * y * z, 1 - 2 * sqx - 2 * sqz);

return new THREE.Vector3(pitch, roll, yaw);
}

class OrbitOrientationControls {
constructor(options) {
this.object = options.camera;
this.domElement = options.canvas;
this.orbit = new OrbitControls(this.object, this.domElement);

this.speed = 0.5;
this.orbit.target.set(0, 0, -1);
this.orbit.enableZoom = false;
this.orbit.enablePan = false;
this.orbit.rotateSpeed = -this.speed;

// if orientation is supported
if (options.orientation) {
this.orientation = new DeviceOrientationControls(this.object);
}
}

update() {
// orientation updates the camera using quaternions and
// orbit updates the camera using angles. They are incompatible
// and one update overrides the other. So before
// orbit overrides orientation we convert our quaternion changes to
// an angle change. Then save the angle into orbit so that
// it will take those into account when it updates the camera and overrides
// our changes
if (this.orientation) {
this.orientation.update();

const quat = this.orientation.object.quaternion;
const currentAngle = Quat2Angle(quat.x, quat.y, quat.z, quat.w);

// we also have to store the last angle since quaternions are b
if (typeof this.lastAngle_ === 'undefined') {
this.lastAngle_ = currentAngle;
}

this.orbit.rotateLeft((this.lastAngle_.z - currentAngle.z) * (1 + this.speed));
this.orbit.rotateUp((this.lastAngle_.y - currentAngle.y) * (1 + this.speed));
this.lastAngle_ = currentAngle;
}

this.orbit.update();
}

dispose() {
this.orbit.dispose();

if (this.orientation) {
this.orientation.dispose();
}
}

}

export default OrbitOrientationControls;
27 changes: 16 additions & 11 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import videojs from 'video.js';
import * as THREE from 'three';
import VRControls from 'three/examples/js/controls/VRControls.js';
import VREffect from 'three/examples/js/effects/VREffect.js';
import OrbitControls from 'three/examples/js/controls/OrbitControls.js';
import OrbitOrientationContols from './orbit-orientation-controls.js';
import * as utils from './utils';

// import controls so they get regisetered with videojs
Expand Down Expand Up @@ -70,10 +70,10 @@ class VR extends Plugin {
}

this.polyfill_ = new WebVRPolyfill({
TOUCH_PANNER_DISABLED: false,
// do not show rotate instructions
ROTATE_INSTRUCTIONS_DISABLED: true
});
this.polyfill_ = new WebVRPolyfill();

this.handleVrDisplayActivate_ = videojs.bind(this, this.handleVrDisplayActivate_);
this.handleVrDisplayDeactivate_ = videojs.bind(this, this.handleVrDisplayDeactivate_);
Expand Down Expand Up @@ -476,22 +476,27 @@ class VR extends Plugin {
if (displays.length > 0) {
this.log('VR Displays found', displays);
this.vrDisplay = displays[0];
this.log('Going to use VRControls on the first one', this.vrDisplay);

// Native WebVR Head Mounted Displays (HMDs) like the HTC Vive
// also need the cardboard button to enter fully immersive mode
// so, we want to add the button if we're not polyfilled.
if (!this.vrDisplay.isPolyfilled) {
this.log('Real HMD found using VRControls', this.vrDisplay);
this.addCardboardButton_();

// We use VRControls here since we are working with an HMD
// and we only want orientation controls.
this.controls3d = new VRControls(this.camera);
}
this.controls3d = new VRControls(this.camera);
} else {
this.log('no vr displays found going to use OrbitControls');
this.controls3d = new OrbitControls(this.camera, this.renderedCanvas);
this.controls3d.target.set(0, 0, -1);
this.controls3d.enableZoom = false;
this.controls3d.enablePan = false;
this.controls3d.rotateSpeed = -0.5;
}

if (!this.controls3d) {
this.log('no HMD found Using Orbit & Orientation Controls');
this.controls3d = new OrbitOrientationContols({
camera: this.camera,
canvas: this.renderedCanvas,
orientation: videojs.browser.IS_IOS || videojs.browser.IS_ANDROID || false
});
}
this.animationFrameId_ = this.requestAnimationFrame(this.animate_);
});
Expand Down

0 comments on commit 0cde016

Please sign in to comment.