From 6a76537ced7b1f9abb82c84d33db404fb055e981 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Tue, 31 Aug 2021 17:13:47 -0400 Subject: [PATCH] feat: vendor threejs files (#247) Copy the example files we depended on from an older version of threejs into a vendor directory and apply the rollup replace pieces directly to it. This allows us to update to a newer version of threejs so that npm audit --production no longer produces any issues. We can't upgrade to newer versions of threejs until we update our tooling and likely start transpiling threejs as well, because by default it starts including new JS features in the build files. Fixes #244. --- package-lock.json | 158 ++-- package.json | 3 +- scripts/rollup-example-resolver.js | 13 - scripts/rollup-replace.js | 52 - scripts/rollup.config.js | 19 +- src/canvas-player-controls.js | 2 +- src/orbit-orientation-controls.js | 4 +- src/plugin.js | 7 +- vendor/three/DeviceOrientationControls.js | 116 +++ vendor/three/OrbitControls.js | 1044 +++++++++++++++++++++ vendor/three/VRControls.js | 154 +++ vendor/three/VREffect.js | 538 +++++++++++ 12 files changed, 1947 insertions(+), 163 deletions(-) delete mode 100644 scripts/rollup-example-resolver.js delete mode 100644 scripts/rollup-replace.js create mode 100644 vendor/three/DeviceOrientationControls.js create mode 100644 vendor/three/OrbitControls.js create mode 100644 vendor/three/VRControls.js create mode 100644 vendor/three/VREffect.js diff --git a/package-lock.json b/package-lock.json index 489796dc..46e499ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1547,29 +1547,45 @@ } }, "@videojs/http-streaming": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-1.11.2.tgz", - "integrity": "sha512-v9Z6wEpGUFxNdnGdaqaMAjosQbkdH3HoO31mYk+cyCoeXYKZYmAfd+Lf5Cwkle+EVqqcXBGfz27aaBUeaQ0vpg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.10.2.tgz", + "integrity": "sha512-JTAlAUHzj0sTsge2WBh4DWKM2I5BDFEZYOvzxmsK/ySILmI0GRyjAHx9uid68ZECQ2qbEAIRmZW5lWp0R5PeNA==", "requires": { - "aes-decrypter": "3.0.0", - "global": "^4.3.0", - "m3u8-parser": "4.4.0", - "mpd-parser": "0.8.1", - "mux.js": "5.4.0", - "url-toolkit": "^2.1.3", - "video.js": "^6.8.0 || ^7.0.0" + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "3.0.3", + "aes-decrypter": "3.1.2", + "global": "^4.4.0", + "m3u8-parser": "4.7.0", + "mpd-parser": "0.19.0", + "mux.js": "5.13.0", + "video.js": "^6 || ^7" + } + }, + "@videojs/vhs-utils": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.3.tgz", + "integrity": "sha512-bU7daxDHhzcTDbmty1cXjzsTYvx2cBGbA8hG5H2Gvpuk4sdfuvkZtMCwtCqL59p6dsleMPspyaNS+7tWXx2Y0A==", + "requires": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" } }, "@videojs/xhr": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.5.1.tgz", - "integrity": "sha512-wV9nGESHseSK+S9ePEru2+OJZ1jq/ZbbzniGQ4weAmTIepuBMSYPx5zrxxQA0E786T5ykpO8ts+LayV+3/oI2w==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==", "requires": { "@babel/runtime": "^7.5.5", "global": "~4.4.0", "is-function": "^1.0.1" } }, + "@xmldom/xmldom": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.2.tgz", + "integrity": "sha512-t/Zqo0ewes3iq6zGqEqJNUWI27Acr3jkmSUNp6E3nl0Z2XbtqAG5XYqPNLdYonILmhcxANsIidh69tHzjXtuRg==" + }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -1621,13 +1637,14 @@ "dev": true }, "aes-decrypter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.0.0.tgz", - "integrity": "sha1-eEihwUW5/b9Xrj4rWxvHzwZEqPs=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.2.tgz", + "integrity": "sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==", "requires": { - "commander": "^2.9.0", - "global": "^4.3.2", - "pkcs7": "^1.0.2" + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.0", + "global": "^4.4.0", + "pkcs7": "^1.0.4" } }, "agent-base": { @@ -2686,7 +2703,8 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true }, "comment-parser": { "version": "1.1.5", @@ -5620,9 +5638,9 @@ "dev": true }, "is-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", - "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" }, "is-glob": { "version": "4.0.1", @@ -6844,11 +6862,13 @@ } }, "m3u8-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.4.0.tgz", - "integrity": "sha512-iH2AygTFILtato+XAgnoPYzLHM4R3DjATj7Ozbk7EHdB2XoLF2oyOUguM7Kc4UVHbQHHL/QPaw98r7PbWzG0gg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.0.tgz", + "integrity": "sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==", "requires": { - "global": "^4.3.2" + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.0", + "global": "^4.4.0" } }, "magic-string": { @@ -7236,12 +7256,14 @@ "dev": true }, "mpd-parser": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.8.1.tgz", - "integrity": "sha512-WBTJ1bKk8OLUIxBh6s1ju1e2yz/5CzhPbgi6P3F3kJHKhGy1Z+ElvEnuzEbtC/dnbRcJtMXazE3f93N5LLdp9Q==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.19.0.tgz", + "integrity": "sha512-FDLIXtZMZs99fv5iXNFg94quNFT26tobo0NUgHu7L3XgZvEq1NBarf5yxDFFJ1zzfbcmtj+NRaml6nYIxoPWvw==", "requires": { - "global": "^4.3.2", - "url-toolkit": "^2.1.1" + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.2", + "@xmldom/xmldom": "^0.7.2", + "global": "^4.4.0" } }, "ms": { @@ -7257,9 +7279,12 @@ "dev": true }, "mux.js": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-5.4.0.tgz", - "integrity": "sha512-UHCJk+Y2ZNU7Wpg+RKDeegr2GKtGjDG1RC+95dVG+OlM2Wz60vpK1LbC3J4D6ehWvtAC8EAaWGzKPzeAiVJxWw==" + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-5.13.0.tgz", + "integrity": "sha512-PkmnzHcTQjUBEHp3KRPQAFoNkJtKlpCEvsYtXDfDrC+/WqbMuxHvoYfmAbHVAH7Sa/KliPVU0dT1ureO8wilog==", + "requires": { + "@babel/runtime": "^7.11.2" + } }, "nanomatch": { "version": "1.2.13", @@ -7865,9 +7890,9 @@ } }, "pkcs7": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.3.tgz", - "integrity": "sha512-3MP+alokz148xIxdMpLovjxIUia1cMzxyJ6FjyZl2a1UPapjRk4Y8hni4KsZRGrdbD1woArGMKe/OsSB7ggzHQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", + "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", "requires": { "@babel/runtime": "^7.5.5" } @@ -9611,9 +9636,9 @@ "dev": true }, "three": { - "version": "0.93.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.93.0.tgz", - "integrity": "sha1-P9bDZ+9FVKu7bhataZNig+iVwSM=" + "version": "0.125.2", + "resolved": "https://registry.npmjs.org/three/-/three-0.125.2.tgz", + "integrity": "sha512-7rIRO23jVKWcAPFdW/HREU2NZMGWPBZ4XwEMt0Ak0jwLUKVJhcKM55eCBWyGZq/KiQbeo1IeuAoo/9l2dzhTXA==" }, "through": { "version": "2.3.8", @@ -10051,9 +10076,9 @@ "dev": true }, "url-toolkit": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.1.6.tgz", - "integrity": "sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw==" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.3.tgz", + "integrity": "sha512-Da75SQoxsZ+2wXS56CZBrj2nukQ4nlGUZUP/dqUBG5E1su5GKThgT94Q00x81eVII7AyS1Pn+CtTTZ4Z0pLUtQ==" }, "use": { "version": "3.1.1", @@ -10129,34 +10154,23 @@ } }, "video.js": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.7.4.tgz", - "integrity": "sha512-iVUlcxNwTog/yyxk85n/mGf8nKrVwHGG9A1np3fcfanKMhHaqk1CubFYYsE9uTtJctEktJqxMFPAXzOjmI6gNg==", - "requires": { - "@babel/runtime": "^7.5.5", - "@videojs/http-streaming": "1.11.2", - "@videojs/xhr": "2.5.1", - "global": "4.3.2", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.15.4.tgz", + "integrity": "sha512-hghxkgptLUvfkpktB4wxcIVF3VpY/hVsMkrjHSv0jpj1bW9Jplzdt8IgpTm9YhlB1KYAp07syVQeZcBFUBwhkw==", + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/http-streaming": "2.10.2", + "@videojs/vhs-utils": "^3.0.3", + "@videojs/xhr": "2.6.0", + "aes-decrypter": "3.1.2", + "global": "^4.4.0", "keycode": "^2.2.0", + "m3u8-parser": "4.7.0", + "mpd-parser": "0.19.0", + "mux.js": "5.13.0", "safe-json-parse": "4.0.0", "videojs-font": "3.2.0", - "videojs-vtt.js": "^0.15.2" - }, - "dependencies": { - "global": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", - "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", - "requires": { - "min-document": "^2.19.0", - "process": "~0.5.1" - } - }, - "process": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" - } + "videojs-vtt.js": "^0.15.3" } }, "videojs-font": { @@ -10275,9 +10289,9 @@ } }, "videojs-vtt.js": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.2.tgz", - "integrity": "sha512-kEo4hNMvu+6KhPvVYPKwESruwhHC3oFis133LwhXHO9U7nRnx0RiJYMiqbgwjgazDEXHR6t8oGJiHM6wq5XlAw==", + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.3.tgz", + "integrity": "sha512-5FvVsICuMRx6Hd7H/Y9s9GDeEtYcXQWzGMS+sl4UX3t/zoHp3y+isSfIPRochnTH7h+Bh1ILyC639xy9Z6kPag==", "requires": { "global": "^4.3.1" } diff --git a/package.json b/package.json index 65a49fde..019f7c0d 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "ignore": [ "dist", "docs", + "vendor", "test/dist" ] }, @@ -80,7 +81,7 @@ "dependencies": { "@babel/runtime": "^7.14.5", "global": "^4.4.0", - "three": "0.93.0", + "three": "0.125.2", "video.js": "^6 || ^7", "webvr-polyfill": "0.10.12" }, diff --git a/scripts/rollup-example-resolver.js b/scripts/rollup-example-resolver.js deleted file mode 100644 index 54a1751c..00000000 --- a/scripts/rollup-example-resolver.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - name: 'example-resolver', - resolveId(source) { - if (source.startsWith('three/examples/js')) { - return { - id: require.resolve(source), - external: false - }; - } - - return null; - }, -} diff --git a/scripts/rollup-replace.js b/scripts/rollup-replace.js deleted file mode 100644 index 02a3edcd..00000000 --- a/scripts/rollup-replace.js +++ /dev/null @@ -1,52 +0,0 @@ -const replace = require('rollup-plugin-re'); - -// three modules to find-replace in -const modules = [ - 'VRControls', - 'VREffect', - 'OrbitControls', - 'DeviceOrientationControls' -]; - -const globalReplace = function(str, pattern, replacement) { - return str.replace(new RegExp(pattern, 'g'), replacement); -}; - -module.exports = replace({ - include: ['node_modules/three/examples/js/**'], - patterns: [ - {transform(code, id) { - modules.forEach((m) => { - if (!(new RegExp(m)).test(id)) { - return; - } - - // trun the global modifiction into an import and a local variable definition - code = code.replace(`THREE.${m} =`, `import * as THREE from 'three';\nvar ${m} =`); - - // change references from the global modification to the local variable - 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'); - // comment out the context menu prevent default line... - code = globalReplace( - code, - "scope.domElement.addEventListener\\( 'contextmenu'", - "\/\/scope.domElement.addEventListener\\( 'contextmenu'" - ); - } - }); - return code; - }} - ] -}); diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js index 11e8efec..ce51c19d 100644 --- a/scripts/rollup.config.js +++ b/scripts/rollup.config.js @@ -1,25 +1,8 @@ const generate = require('videojs-generate-rollup-config'); -const replace = require('./rollup-replace'); -const exampleResolver = require('./rollup-example-resolver'); // see https://github.com/videojs/videojs-generate-rollup-config // for options -const options = { - primedPlugins(defaults) { - return Object.assign(defaults, {exampleResolver,replace}); - }, - plugins(defaults) { - defaults.module.unshift('exampleResolver') - - // add replace just after json for each build - Object.keys(defaults).forEach((type) => { - defaults[type].splice(defaults[type].indexOf('json') + 1, '0', 'replace'); - }); - - return defaults; - } -}; -const config = generate(options); +const config = generate({}); // Add additional builds/customization here! diff --git a/src/canvas-player-controls.js b/src/canvas-player-controls.js index 32e82e0a..8cb62402 100644 --- a/src/canvas-player-controls.js +++ b/src/canvas-player-controls.js @@ -96,7 +96,7 @@ class CanvasPlayerControls extends videojs.EventTarget { } // We want the same behavior in Desktop for VR360 and standard player - if (e.type == 'mouseup') { + if (e.type === 'mouseup') { this.togglePlay(); } diff --git a/src/orbit-orientation-controls.js b/src/orbit-orientation-controls.js index 69e4f398..6b876a31 100644 --- a/src/orbit-orientation-controls.js +++ b/src/orbit-orientation-controls.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; -import OrbitControls from 'three/examples/js/controls/OrbitControls.js'; -import DeviceOrientationControls from 'three/examples/js/controls/DeviceOrientationControls.js'; +import OrbitControls from '../vendor/three/OrbitControls.js'; +import DeviceOrientationControls from '../vendor/three/DeviceOrientationControls.js'; /** * Convert a quaternion to an angle diff --git a/src/plugin.js b/src/plugin.js index de1494e5..3d1fe569 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -4,8 +4,8 @@ import document from 'global/document'; import WebVRPolyfill from 'webvr-polyfill'; 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 VRControls from '../vendor/three/VRControls.js'; +import VREffect from '../vendor/three/VREffect.js'; import OrbitOrientationContols from './orbit-orientation-controls.js'; import * as utils from './utils'; import CanvasPlayerControls from './canvas-player-controls'; @@ -234,7 +234,7 @@ class VR extends Plugin { this.scene.add(this.movieScreen); } else if (projection === '180' || projection === '180_LR' || projection === '180_MONO') { - let geometry = new THREE.SphereGeometry( + let geometry = new THREE.SphereGeometry( 256, this.options_.sphereDetail, this.options_.sphereDetail, @@ -242,7 +242,6 @@ class VR extends Plugin { Math.PI ); - // Left eye view geometry.scale(-1, 1, 1); let uvs = geometry.faceVertexUvs[0]; diff --git a/vendor/three/DeviceOrientationControls.js b/vendor/three/DeviceOrientationControls.js new file mode 100644 index 00000000..a74e99f8 --- /dev/null +++ b/vendor/three/DeviceOrientationControls.js @@ -0,0 +1,116 @@ +/** + * @author richt / http://richt.me + * @author WestLangley / http://github.com/WestLangley + * + * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html) + * + * originally from https://github.com/mrdoob/three.js/blob/r93/examples/js/controls/DeviceOrientationControls.js + */ +import * as THREE from 'three'; + +const DeviceOrientationControls = function ( object ) { + + var scope = this; + + this.object = object; + this.object.rotation.reorder( 'YXZ' ); + + this.enabled = true; + + this.deviceOrientation = {}; + this.screenOrientation = 0; + + this.alphaOffset = 0; // radians + + var onDeviceOrientationChangeEvent = function ( event ) { + + scope.deviceOrientation = event; + + }; + + var onScreenOrientationChangeEvent = function () { + + scope.screenOrientation = window.orientation || 0; + + }; + + // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y'' + + var setObjectQuaternion = function () { + + var zee = new THREE.Vector3( 0, 0, 1 ); + + var euler = new THREE.Euler(); + + var q0 = new THREE.Quaternion(); + + var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis + + return function ( quaternion, alpha, beta, gamma, orient ) { + + euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us + + quaternion.setFromEuler( euler ); // orient the device + + quaternion.multiply( q1 ); // camera looks out the back of the device, not the top + + quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation + + }; + + }(); + + this.connect = function () { + + onScreenOrientationChangeEvent(); // run once on load + + window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); + window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); + + scope.enabled = true; + + }; + + this.disconnect = function () { + + window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); + window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); + + scope.enabled = false; + + }; + + this.update = function () { + + if ( scope.enabled === false ) return; + + var device = scope.deviceOrientation; + + if ( device ) { + + var alpha = device.alpha ? THREE.Math.degToRad( device.alpha ) + scope.alphaOffset : 0; // Z + + var beta = device.beta ? THREE.Math.degToRad( device.beta ) : 0; // X' + + var gamma = device.gamma ? THREE.Math.degToRad( device.gamma ) : 0; // Y'' + + var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O + + setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient ); + + } + + + }; + + this.dispose = function () { + + scope.disconnect(); + + }; + + this.connect(); + +}; + +export default DeviceOrientationControls; diff --git a/vendor/three/OrbitControls.js b/vendor/three/OrbitControls.js new file mode 100644 index 00000000..916893bf --- /dev/null +++ b/vendor/three/OrbitControls.js @@ -0,0 +1,1044 @@ +/** + * @author qiao / https://github.com/qiao + * @author mrdoob / http://mrdoob.com + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author erich666 / http://erichaines.com + * + * originally from https://github.com/mrdoob/three.js/blob/r93/examples/js/controls/OrbitControls.js + */ +import * as THREE from 'three'; + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or arrow keys / touch: two-finger move + +const OrbitControls = function ( object, domElement ) { + + this.object = object; + + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new THREE.Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.25; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = false; // if true, pan in screen-space + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // Set to false to disable use of the keys + this.enableKeys = true; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new THREE.Vector3(); + + // so camera.up is the orbit axis + var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().inverse(); + + var lastPosition = new THREE.Vector3(); + var lastQuaternion = new THREE.Quaternion(); + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + scope.rotateLeft( getAutoRotationAngle() ); + + } + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + // restrict theta to be between desired limits + spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + scope.target.add( panOffset ); + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); + scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + window.removeEventListener( 'keydown', onKeyDown, false ); + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new THREE.Spherical(); + var sphericalDelta = new THREE.Spherical(); + + var scale = 1; + var panOffset = new THREE.Vector3(); + var zoomChanged = false; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + scope.rotateLeft = function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + scope.rotateUp = function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new THREE.Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new THREE.Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new THREE.Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + //console.log( 'handleMouseDownRotate' ); + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + //console.log( 'handleMouseDownDolly' ); + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + //console.log( 'handleMouseDownPan' ); + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + //console.log( 'handleMouseMoveRotate' ); + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + //console.log( 'handleMouseMoveDolly' ); + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyOut( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + //console.log( 'handleMouseMovePan' ); + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( event ) { + + // console.log( 'handleMouseUp' ); + + } + + function handleMouseWheel( event ) { + + // console.log( 'handleMouseWheel' ); + + if ( event.deltaY < 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyIn( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + //console.log( 'handleKeyDown' ); + + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + scope.update(); + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + scope.update(); + break; + + } + + } + + function handleTouchStartRotate( event ) { + + //console.log( 'handleTouchStartRotate' ); + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } + + function handleTouchStartDollyPan( event ) { + + //console.log( 'handleTouchStartDollyPan' ); + + if ( scope.enableZoom ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + if ( scope.enablePan ) { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchMoveRotate( event ) { + + //console.log( 'handleTouchMoveRotate' ); + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleTouchMoveDollyPan( event ) { + + //console.log( 'handleTouchMoveDollyPan' ); + + if ( scope.enableZoom ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyIn( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + if ( scope.enablePan ) { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panEnd.set( x, y ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + scope.update(); + + } + + function handleTouchEnd( event ) { + + //console.log( 'handleTouchEnd' ); + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onMouseDown( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( event.button ) { + + case scope.mouseButtons.ORBIT: + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + break; + + case scope.mouseButtons.ZOOM: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case scope.mouseButtons.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + break; + + } + + if ( state !== STATE.NONE ) { + + document.addEventListener( 'mousemove', onMouseMove, false ); + document.addEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + event.stopPropagation(); + + scope.dispatchEvent( startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case 2: // two-fingered touch: dolly-pan + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan( event ); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.enableRotate === false ) return; + if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed? + + handleTouchMoveRotate( event ); + + break; + + case 2: // two-fingered touch: dolly-pan + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed? + + handleTouchMoveDollyPan( event ); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + // scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); + + scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); + + window.addEventListener( 'keydown', onKeyDown, false ); + + // force an update at start + + this.update(); + +}; + +OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +OrbitControls.prototype.constructor = OrbitControls; + +Object.defineProperties( OrbitControls.prototype, { + + center: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); + return this.target; + + } + + }, + + // backward compatibility + + noZoom: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); + return ! this.enableZoom; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); + this.enableZoom = ! value; + + } + + }, + + noRotate: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); + return ! this.enableRotate; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); + this.enableRotate = ! value; + + } + + }, + + noPan: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); + return ! this.enablePan; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); + this.enablePan = ! value; + + } + + }, + + noKeys: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); + return ! this.enableKeys; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); + this.enableKeys = ! value; + + } + + }, + + staticMoving: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); + return ! this.enableDamping; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); + this.enableDamping = ! value; + + } + + }, + + dynamicDampingFactor: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); + return this.dampingFactor; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); + this.dampingFactor = value; + + } + + } + +} ); + +export default OrbitControls; diff --git a/vendor/three/VRControls.js b/vendor/three/VRControls.js new file mode 100644 index 00000000..c5f6008c --- /dev/null +++ b/vendor/three/VRControls.js @@ -0,0 +1,154 @@ +/** + * @author dmarcos / https://github.com/dmarcos + * @author mrdoob / http://mrdoob.com + * + * originally from https://github.com/mrdoob/three.js/blob/r93/examples/js/controls/VRControls.js + */ +import * as THREE from 'three'; + +const VRControls = function ( object, onError ) { + + var scope = this; + + var vrDisplay, vrDisplays; + + var standingMatrix = new THREE.Matrix4(); + + var frameData = null; + + if ( 'VRFrameData' in window ) { + + frameData = new VRFrameData(); + + } + + function gotVRDisplays( displays ) { + + vrDisplays = displays; + + if ( displays.length > 0 ) { + + vrDisplay = displays[ 0 ]; + + } else { + + if ( onError ) onError( 'VR input not available.' ); + + } + + } + + if ( navigator.getVRDisplays ) { + + navigator.getVRDisplays().then( gotVRDisplays ).catch( function () { + + console.warn( 'THREE.VRControls: Unable to get VR Displays' ); + + } ); + + } + + // the Rift SDK returns the position in meters + // this scale factor allows the user to define how meters + // are converted to scene units. + + this.scale = 1; + + // If true will use "standing space" coordinate system where y=0 is the + // floor and x=0, z=0 is the center of the room. + this.standing = false; + + // Distance from the users eyes to the floor in meters. Used when + // standing=true but the VRDisplay doesn't provide stageParameters. + this.userHeight = 1.6; + + this.getVRDisplay = function () { + + return vrDisplay; + + }; + + this.setVRDisplay = function ( value ) { + + vrDisplay = value; + + }; + + this.getVRDisplays = function () { + + console.warn( 'THREE.VRControls: getVRDisplays() is being deprecated.' ); + return vrDisplays; + + }; + + this.getStandingMatrix = function () { + + return standingMatrix; + + }; + + this.update = function () { + + if ( vrDisplay ) { + + var pose; + + if ( vrDisplay.getFrameData ) { + + vrDisplay.getFrameData( frameData ); + pose = frameData.pose; + + } else if ( vrDisplay.getPose ) { + + pose = vrDisplay.getPose(); + + } + + if ( pose.orientation !== null ) { + + object.quaternion.fromArray( pose.orientation ); + + } + + if ( pose.position !== null ) { + + object.position.fromArray( pose.position ); + + } else { + + object.position.set( 0, 0, 0 ); + + } + + if ( this.standing ) { + + if ( vrDisplay.stageParameters ) { + + object.updateMatrix(); + + standingMatrix.fromArray( vrDisplay.stageParameters.sittingToStandingTransform ); + object.applyMatrix( standingMatrix ); + + } else { + + object.position.setY( object.position.y + this.userHeight ); + + } + + } + + object.position.multiplyScalar( scope.scale ); + + } + + }; + + this.dispose = function () { + + vrDisplay = null; + + }; + +}; + +export default VRControls; diff --git a/vendor/three/VREffect.js b/vendor/three/VREffect.js new file mode 100644 index 00000000..cbc3630f --- /dev/null +++ b/vendor/three/VREffect.js @@ -0,0 +1,538 @@ +/** + * @author dmarcos / https://github.com/dmarcos + * @author mrdoob / http://mrdoob.com + * + * WebVR Spec: http://mozvr.github.io/webvr-spec/webvr.html + * + * Firefox: http://mozvr.com/downloads/ + * Chromium: https://webvr.info/get-chrome + * + * originally from https://github.com/mrdoob/three.js/blob/r93/examples/js/effects/VREffect.js + */ +import * as THREE from 'three'; + +const VREffect = function ( renderer, onError ) { + + var vrDisplay, vrDisplays; + var eyeTranslationL = new THREE.Vector3(); + var eyeTranslationR = new THREE.Vector3(); + var renderRectL, renderRectR; + var headMatrix = new THREE.Matrix4(); + var eyeMatrixL = new THREE.Matrix4(); + var eyeMatrixR = new THREE.Matrix4(); + + var frameData = null; + + if ( 'VRFrameData' in window ) { + + frameData = new window.VRFrameData(); + + } + + function gotVRDisplays( displays ) { + + vrDisplays = displays; + + if ( displays.length > 0 ) { + + vrDisplay = displays[ 0 ]; + + } else { + + if ( onError ) onError( 'HMD not available' ); + + } + + } + + if ( navigator.getVRDisplays ) { + + navigator.getVRDisplays().then( gotVRDisplays ).catch( function () { + + console.warn( 'THREE.VREffect: Unable to get VR Displays' ); + + } ); + + } + + // + + this.isPresenting = false; + + var scope = this; + + var rendererSize = renderer.getSize(); + var rendererUpdateStyle = false; + var rendererPixelRatio = renderer.getPixelRatio(); + + this.getVRDisplay = function () { + + return vrDisplay; + + }; + + this.setVRDisplay = function ( value ) { + + vrDisplay = value; + + }; + + this.getVRDisplays = function () { + + console.warn( 'THREE.VREffect: getVRDisplays() is being deprecated.' ); + return vrDisplays; + + }; + + this.setSize = function ( width, height, updateStyle ) { + + rendererSize = { width: width, height: height }; + rendererUpdateStyle = updateStyle; + + if ( scope.isPresenting ) { + + var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); + renderer.setPixelRatio( 1 ); + renderer.setSize( eyeParamsL.renderWidth * 2, eyeParamsL.renderHeight, false ); + + } else { + + renderer.setPixelRatio( rendererPixelRatio ); + renderer.setSize( width, height, updateStyle ); + + } + + }; + + // VR presentation + + var canvas = renderer.domElement; + var defaultLeftBounds = [ 0.0, 0.0, 0.5, 1.0 ]; + var defaultRightBounds = [ 0.5, 0.0, 0.5, 1.0 ]; + + function onVRDisplayPresentChange() { + + var wasPresenting = scope.isPresenting; + scope.isPresenting = vrDisplay !== undefined && vrDisplay.isPresenting; + + if ( scope.isPresenting ) { + + var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); + var eyeWidth = eyeParamsL.renderWidth; + var eyeHeight = eyeParamsL.renderHeight; + + if ( ! wasPresenting ) { + + rendererPixelRatio = renderer.getPixelRatio(); + rendererSize = renderer.getSize(); + + renderer.setPixelRatio( 1 ); + renderer.setSize( eyeWidth * 2, eyeHeight, false ); + + } + + } else if ( wasPresenting ) { + + renderer.setPixelRatio( rendererPixelRatio ); + renderer.setSize( rendererSize.width, rendererSize.height, rendererUpdateStyle ); + + } + + } + + window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false ); + + this.setFullScreen = function ( boolean ) { + + return new Promise( function ( resolve, reject ) { + + if ( vrDisplay === undefined ) { + + reject( new Error( 'No VR hardware found.' ) ); + return; + + } + + if ( scope.isPresenting === boolean ) { + + resolve(); + return; + + } + + if ( boolean ) { + + resolve( vrDisplay.requestPresent( [ { source: canvas } ] ) ); + + } else { + + resolve( vrDisplay.exitPresent() ); + + } + + } ); + + }; + + this.requestPresent = function () { + + return this.setFullScreen( true ); + + }; + + this.exitPresent = function () { + + return this.setFullScreen( false ); + + }; + + this.requestAnimationFrame = function ( f ) { + + if ( vrDisplay !== undefined ) { + + return vrDisplay.requestAnimationFrame( f ); + + } else { + + return window.requestAnimationFrame( f ); + + } + + }; + + this.cancelAnimationFrame = function ( h ) { + + if ( vrDisplay !== undefined ) { + + vrDisplay.cancelAnimationFrame( h ); + + } else { + + window.cancelAnimationFrame( h ); + + } + + }; + + this.submitFrame = function () { + + if ( vrDisplay !== undefined && scope.isPresenting ) { + + vrDisplay.submitFrame(); + + } + + }; + + this.autoSubmitFrame = true; + + // render + + var cameraL = new THREE.PerspectiveCamera(); + cameraL.layers.enable( 1 ); + + var cameraR = new THREE.PerspectiveCamera(); + cameraR.layers.enable( 2 ); + + this.render = function ( scene, camera, renderTarget, forceClear ) { + + if ( vrDisplay && scope.isPresenting ) { + + var autoUpdate = scene.autoUpdate; + + if ( autoUpdate ) { + + scene.updateMatrixWorld(); + scene.autoUpdate = false; + + } + + if ( Array.isArray( scene ) ) { + + console.warn( 'THREE.VREffect.render() no longer supports arrays. Use object.layers instead.' ); + scene = scene[ 0 ]; + + } + + // When rendering we don't care what the recommended size is, only what the actual size + // of the backbuffer is. + var size = renderer.getSize(); + var layers = vrDisplay.getLayers(); + var leftBounds; + var rightBounds; + + if ( layers.length ) { + + var layer = layers[ 0 ]; + + leftBounds = layer.leftBounds !== null && layer.leftBounds.length === 4 ? layer.leftBounds : defaultLeftBounds; + rightBounds = layer.rightBounds !== null && layer.rightBounds.length === 4 ? layer.rightBounds : defaultRightBounds; + + } else { + + leftBounds = defaultLeftBounds; + rightBounds = defaultRightBounds; + + } + + renderRectL = { + x: Math.round( size.width * leftBounds[ 0 ] ), + y: Math.round( size.height * leftBounds[ 1 ] ), + width: Math.round( size.width * leftBounds[ 2 ] ), + height: Math.round( size.height * leftBounds[ 3 ] ) + }; + renderRectR = { + x: Math.round( size.width * rightBounds[ 0 ] ), + y: Math.round( size.height * rightBounds[ 1 ] ), + width: Math.round( size.width * rightBounds[ 2 ] ), + height: Math.round( size.height * rightBounds[ 3 ] ) + }; + + if ( renderTarget ) { + + renderer.setRenderTarget( renderTarget ); + renderTarget.scissorTest = true; + + } else { + + renderer.setRenderTarget( null ); + renderer.setScissorTest( true ); + + } + + if ( renderer.autoClear || forceClear ) renderer.clear(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale ); + + cameraR.position.copy( cameraL.position ); + cameraR.quaternion.copy( cameraL.quaternion ); + cameraR.scale.copy( cameraL.scale ); + + if ( vrDisplay.getFrameData ) { + + vrDisplay.depthNear = camera.near; + vrDisplay.depthFar = camera.far; + + vrDisplay.getFrameData( frameData ); + + cameraL.projectionMatrix.elements = frameData.leftProjectionMatrix; + cameraR.projectionMatrix.elements = frameData.rightProjectionMatrix; + + getEyeMatrices( frameData ); + + cameraL.updateMatrix(); + cameraL.matrix.multiply( eyeMatrixL ); + cameraL.matrix.decompose( cameraL.position, cameraL.quaternion, cameraL.scale ); + + cameraR.updateMatrix(); + cameraR.matrix.multiply( eyeMatrixR ); + cameraR.matrix.decompose( cameraR.position, cameraR.quaternion, cameraR.scale ); + + } else { + + var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); + var eyeParamsR = vrDisplay.getEyeParameters( 'right' ); + + cameraL.projectionMatrix = fovToProjection( eyeParamsL.fieldOfView, true, camera.near, camera.far ); + cameraR.projectionMatrix = fovToProjection( eyeParamsR.fieldOfView, true, camera.near, camera.far ); + + eyeTranslationL.fromArray( eyeParamsL.offset ); + eyeTranslationR.fromArray( eyeParamsR.offset ); + + cameraL.translateOnAxis( eyeTranslationL, cameraL.scale.x ); + cameraR.translateOnAxis( eyeTranslationR, cameraR.scale.x ); + + } + + // render left eye + if ( renderTarget ) { + + renderTarget.viewport.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); + renderTarget.scissor.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); + + } else { + + renderer.setViewport( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); + renderer.setScissor( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); + + } + renderer.render( scene, cameraL, renderTarget, forceClear ); + + // render right eye + if ( renderTarget ) { + + renderTarget.viewport.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); + renderTarget.scissor.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); + + } else { + + renderer.setViewport( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); + renderer.setScissor( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); + + } + renderer.render( scene, cameraR, renderTarget, forceClear ); + + if ( renderTarget ) { + + renderTarget.viewport.set( 0, 0, size.width, size.height ); + renderTarget.scissor.set( 0, 0, size.width, size.height ); + renderTarget.scissorTest = false; + renderer.setRenderTarget( null ); + + } else { + + renderer.setViewport( 0, 0, size.width, size.height ); + renderer.setScissorTest( false ); + + } + + if ( autoUpdate ) { + + scene.autoUpdate = true; + + } + + if ( scope.autoSubmitFrame ) { + + scope.submitFrame(); + + } + + return; + + } + + // Regular render mode if not HMD + + renderer.render( scene, camera, renderTarget, forceClear ); + + }; + + this.dispose = function () { + + window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false ); + + }; + + // + + var poseOrientation = new THREE.Quaternion(); + var posePosition = new THREE.Vector3(); + + // Compute model matrices of the eyes with respect to the head. + function getEyeMatrices( frameData ) { + + // Compute the matrix for the position of the head based on the pose + if ( frameData.pose.orientation ) { + + poseOrientation.fromArray( frameData.pose.orientation ); + headMatrix.makeRotationFromQuaternion( poseOrientation ); + + } else { + + headMatrix.identity(); + + } + + if ( frameData.pose.position ) { + + posePosition.fromArray( frameData.pose.position ); + headMatrix.setPosition( posePosition ); + + } + + // The view matrix transforms vertices from sitting space to eye space. As such, the view matrix can be thought of as a product of two matrices: + // headToEyeMatrix * sittingToHeadMatrix + + // The headMatrix that we've calculated above is the model matrix of the head in sitting space, which is the inverse of sittingToHeadMatrix. + // So when we multiply the view matrix with headMatrix, we're left with headToEyeMatrix: + // viewMatrix * headMatrix = headToEyeMatrix * sittingToHeadMatrix * headMatrix = headToEyeMatrix + + eyeMatrixL.fromArray( frameData.leftViewMatrix ); + eyeMatrixL.multiply( headMatrix ); + eyeMatrixR.fromArray( frameData.rightViewMatrix ); + eyeMatrixR.multiply( headMatrix ); + + // The eye's model matrix in head space is the inverse of headToEyeMatrix we calculated above. + + eyeMatrixL.getInverse( eyeMatrixL ); + eyeMatrixR.getInverse( eyeMatrixR ); + + } + + function fovToNDCScaleOffset( fov ) { + + var pxscale = 2.0 / ( fov.leftTan + fov.rightTan ); + var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5; + var pyscale = 2.0 / ( fov.upTan + fov.downTan ); + var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5; + return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] }; + + } + + function fovPortToProjection( fov, rightHanded, zNear, zFar ) { + + rightHanded = rightHanded === undefined ? true : rightHanded; + zNear = zNear === undefined ? 0.01 : zNear; + zFar = zFar === undefined ? 10000.0 : zFar; + + var handednessScale = rightHanded ? - 1.0 : 1.0; + + // start with an identity matrix + var mobj = new THREE.Matrix4(); + var m = mobj.elements; + + // and with scale/offset info for normalized device coords + var scaleAndOffset = fovToNDCScaleOffset( fov ); + + // X result, map clip edges to [-w,+w] + m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ]; + m[ 0 * 4 + 1 ] = 0.0; + m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale; + m[ 0 * 4 + 3 ] = 0.0; + + // Y result, map clip edges to [-w,+w] + // Y offset is negated because this proj matrix transforms from world coords with Y=up, + // but the NDC scaling has Y=down (thanks D3D?) + m[ 1 * 4 + 0 ] = 0.0; + m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ]; + m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale; + m[ 1 * 4 + 3 ] = 0.0; + + // Z result (up to the app) + m[ 2 * 4 + 0 ] = 0.0; + m[ 2 * 4 + 1 ] = 0.0; + m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale; + m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar ); + + // W result (= Z in) + m[ 3 * 4 + 0 ] = 0.0; + m[ 3 * 4 + 1 ] = 0.0; + m[ 3 * 4 + 2 ] = handednessScale; + m[ 3 * 4 + 3 ] = 0.0; + + mobj.transpose(); + return mobj; + + } + + function fovToProjection( fov, rightHanded, zNear, zFar ) { + + var DEG2RAD = Math.PI / 180.0; + + var fovPort = { + upTan: Math.tan( fov.upDegrees * DEG2RAD ), + downTan: Math.tan( fov.downDegrees * DEG2RAD ), + leftTan: Math.tan( fov.leftDegrees * DEG2RAD ), + rightTan: Math.tan( fov.rightDegrees * DEG2RAD ) + }; + + return fovPortToProjection( fovPort, rightHanded, zNear, zFar ); + + } + +}; + +export default VREffect;