diff --git a/README.md b/README.md index a3e7026c4e4d15a72fbdef4629ddc756f579f112..d42dacae191ad8c4902202e5ca08fbb7b8a72404 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ To bundle javascript and generate the html templates, run: yarn build ``` +## hubs.local Host Entry + +When running the full stack for Hubs (which includes [Reticulum](https://github.com/mozilla/reticulum)) +locally it is necessary to add a `hosts` entry pointing `hubs.local` to your local server's IP. +This will allow the CSP checks to pass that are served up by Reticulum so you can test the whole app. + ## Query Params - `room` - Id of the room (an integer) that you want to join @@ -31,7 +37,7 @@ yarn build - `quality` - Either "low" or "high". Force assets to a certain quality level - `mobile` - Force mobile mode - `no_stats` - Disable performance stats -- `vr_entry_type` - Either "gearvr" or "daydream". Used internally to force a VR entry type +- `vr_entry_type` - Either "2d", "vr", or "daydream". Used internally to force a VR entry type. Add "_now" to the end of the value to skip the audio check. - `disable_telemetry` - If `true` disables Sentry telemetry. - `log_filter` - A `debug` style filter for setting the logging level. - `debug` - If `true` performs verbose logging of Janus and NAF traffic. diff --git a/scripts/bot/run-bot.js b/scripts/bot/run-bot.js old mode 100644 new mode 100755 index cadf6aaf2a1a2a444b290fc64cb2ad1cba3a895a..dc3cb0160099bb1e437af9c8ea3065e7eb84c0aa --- a/scripts/bot/run-bot.js +++ b/scripts/bot/run-bot.js @@ -2,7 +2,6 @@ const doc = ` Usage: ./run-bot.js [options] - Options: -u --url=<url> URL -o --host=<host> Hubs host if URL is not specified [default: localhost:8080] @@ -16,12 +15,20 @@ const options = docopt(doc); const puppeteer = require("puppeteer"); const querystring = require("query-string"); +function log(...objs) { + console.log.call(null, [new Date().toISOString()].concat(objs).join(" ")); +} + +function error(...objs) { + console.error.call(null, [new Date().toISOString()].concat(objs).join(" ")); +} + (async () => { const browser = await puppeteer.launch({ ignoreHTTPSErrors: true }); const page = await browser.newPage(); - page.on("console", msg => console.log("PAGE: ", msg.text())); - page.on("error", err => console.error("ERROR: ", err.toString().split("\n")[0])); - page.on("pageerror", err => console.error("PAGE ERROR: ", err.toString().split("\n")[0])); + page.on("console", msg => log("PAGE: ", msg.text())); + page.on("error", err => error("ERROR: ", err.toString().split("\n")[0])); + page.on("pageerror", err => error("PAGE ERROR: ", err.toString().split("\n")[0])); const baseUrl = options["--url"] || `https://${options["--host"]}/hub.html`; @@ -35,11 +42,11 @@ const querystring = require("query-string"); } const url = `${baseUrl}?${querystring.stringify(params)}`; - console.log(url); + log(url); const navigate = async () => { try { - console.log("Spawning bot..."); + log("Spawning bot..."); await page.goto(url); await page.evaluate(() => console.log(navigator.userAgent)); let retryCount = 5; @@ -50,14 +57,14 @@ const querystring = require("query-string"); await page.mouse.click(100, 100); // Signal that the page has been interacted with. await page.evaluate(() => window.interacted()); - console.log("Interacted."); + log("Interacted."); } catch (e) { - console.log("Interaction error", e.message); + log("Interaction error", e.message); if (retryCount-- < 0) { // If retries failed, throw and restart navigation. throw new Error("Retries failed"); } - console.log("Retrying..."); + log("Retrying..."); backoff *= 2; // Retry interaction to start audio playback setTimeout(interact, backoff); @@ -65,7 +72,7 @@ const querystring = require("query-string"); }; await interact(); } catch (e) { - console.log("Navigation error", e.message); + log("Navigation error", e.message); setTimeout(navigate, 1000); } }; diff --git a/scripts/build_local_reticulum.sh b/scripts/build_local_reticulum.sh index 3f5e3a00136a84adf99b68926ee0dd1b17a09af3..9a19f9f202b688b27213b0478f9e1a14e67d2620 100755 --- a/scripts/build_local_reticulum.sh +++ b/scripts/build_local_reticulum.sh @@ -4,4 +4,4 @@ if [ ! -e ../reticulum ]; then echo "This script assumes reticulum is checked out in a sibling to this folder." fi -rm -rf ../reticulum/priv/static ; GENERATE_SMOKE_TESTS=true BASE_ASSETS_PATH=https://localhost:4000/ yarn build -- --output-path ../reticulum/priv/static +rm -rf ../reticulum/priv/static ; GENERATE_SMOKE_TESTS=true BASE_ASSETS_PATH=https://hubs.local:4000/ yarn build -- --output-path ../reticulum/priv/static diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index 0201cf0fca4a81f60217ab97679f6f2e7316fc43..79d11599544849a83f9982617dafddd3bb6c268a 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -3,6 +3,7 @@ "entry.screen-prefix": "Enter on ", "entry.desktop-screen": "Screen", "entry.mobile-screen": "Phone", + "entry.mobile-safari": "Safari", "entry.generic-prefix": "Enter in ", "entry.generic-medium": "VR", "entry.generic-subtitle-desktop": "Oculus or SteamVR", diff --git a/src/avatar-selector.html b/src/avatar-selector.html index 96dd9c67463ec3ddda58956463b2b136b005ed27..d125ca51622c0ed77dec9b1285e2a1b0380c8a27 100644 --- a/src/avatar-selector.html +++ b/src/avatar-selector.html @@ -6,9 +6,9 @@ <title>avatar selector</title> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> <% if(NODE_ENV === "production") { %> - <script src="https://cdn.rawgit.com/aframevr/aframe/3e7a4b3/dist/aframe-master.min.js" integrity="sha384-LQXa4VjhYucs9sVd5yQ3OhBXRea0jrvbHJA8CYLgTnvzxF5uvyhabSo1mX4tT2c6" crossorigin="anonymous"></script> + <script src="https://cdn.rawgit.com/aframevr/aframe/1be48d9/dist/aframe-master.min.js" integrity="sha384-SXrfoMHbXpA5RZhIyhgaR6tQ764dDZsbFk3PiokC/tc0+NnW1yaYQMUzWtL06hnq" crossorigin="anonymous"></script> <% } else { %> - <script src="https://cdn.rawgit.com/aframevr/aframe/3e7a4b3/dist/aframe-master.js" integrity="sha384-EaMOuyBOi9ERV/lVDwQgz/yFWBDWPsIju5Co6oCZZHXuvbLBO81yPWn80q0BbBn3" crossorigin="anonymous"></script> + <script src="https://cdn.rawgit.com/aframevr/aframe/1be48d9/dist/aframe-master.js" integrity="sha384-AmjDGOMbvTrrUFdeVWcBIlXRINIWnO8iwj/4VS21OWbYDsa/7nheOIyPAPJSkR6J" crossorigin="anonymous"></script> <% } %> </head> diff --git a/src/components/audio-feedback.js b/src/components/audio-feedback.js index 7edf3ec3f654eb9ee7b45c35c58d2afee2c56373..aa7182fa7329145078a1ff27270700164444efff 100644 --- a/src/components/audio-feedback.js +++ b/src/components/audio-feedback.js @@ -4,8 +4,8 @@ * @component networked-audio-analyser */ AFRAME.registerComponent("networked-audio-analyser", { - schema: {}, async init() { + this.volume = 0; this.el.addEventListener("sound-source-set", event => { const ctx = THREE.AudioContext.getContext(); this.analyser = ctx.createAnalyser(); @@ -25,10 +25,6 @@ AFRAME.registerComponent("networked-audio-analyser", { sum += this.levels[i]; } this.volume = sum / this.levels.length; - this.el.emit("audioFrequencyChange", { - volume: this.volume, - levels: this.levels - }); } }); @@ -37,24 +33,12 @@ AFRAME.registerComponent("networked-audio-analyser", { * @component matcolor-audio-feedback */ AFRAME.registerComponent("matcolor-audio-feedback", { - schema: { - analyserSrc: { type: "selector" } - }, - init: function() { - this.onAudioFrequencyChange = this.onAudioFrequencyChange.bind(this); - }, + tick() { + const audioAnalyser = this.el.components["networked-audio-analyser"]; - play() { - (this.data.analyserSrc || this.el).addEventListener("audioFrequencyChange", this.onAudioFrequencyChange); - }, + if (!audioAnalyser || !this.mat) return; - pause() { - (this.data.analyserSrc || this.el).removeEventListener("audioFrequencyChange", this.onAudioFrequencyChange); - }, - - onAudioFrequencyChange(e) { - if (!this.mat) return; - this.object3D.mesh.color.setScalar(1 + e.detail.volume / 255 * 2); + this.object3D.mesh.color.setScalar(1 + audioAnalyser.volume / 255 * 2); } }); @@ -65,29 +49,21 @@ AFRAME.registerComponent("matcolor-audio-feedback", { */ AFRAME.registerComponent("scale-audio-feedback", { schema: { - analyserSrc: { type: "selector" }, - minScale: { default: 1 }, maxScale: { default: 2 } }, - init() { - this.onAudioFrequencyChange = this.onAudioFrequencyChange.bind(this); - }, - - play() { - (this.data.analyserSrc || this.el).addEventListener("audioFrequencyChange", this.onAudioFrequencyChange); - }, - - pause() { - (this.data.analyserSrc || this.el).removeEventListener("audioFrequencyChange", this.onAudioFrequencyChange); - }, - - onAudioFrequencyChange(e) { + tick() { // TODO: come up with a cleaner way to handle this. // bone's are "hidden" by scaling them with bone-visibility, without this we would overwrite that. if (!this.el.object3D.visible) return; + const { minScale, maxScale } = this.data; - this.el.object3D.scale.setScalar(minScale + (maxScale - minScale) * e.detail.volume / 255); + + const audioAnalyser = this.el.components["networked-audio-analyser"]; + + if (!audioAnalyser) return; + + this.el.object3D.scale.setScalar(minScale + (maxScale - minScale) * audioAnalyser.volume / 255); } }); diff --git a/src/components/character-controller.js b/src/components/character-controller.js index d3e36432d93217d812e54e6a1ca9531d1bd12fb3..f32debe666107ccfb6144f94e7ca5afe3f9f4168 100644 --- a/src/components/character-controller.js +++ b/src/components/character-controller.js @@ -125,23 +125,25 @@ AFRAME.registerComponent("character-controller", { yawMatrix.makeRotationAxis(rotationAxis, rotationDelta); // Translate to middle of playspace (player rig) - root.applyMatrix(transInv); + root.matrix.premultiply(transInv); // Zero playspace (player rig) rotation - root.applyMatrix(rotationInvMatrix); + root.matrix.premultiply(rotationInvMatrix); // Zero pivot (camera/head) rotation - root.applyMatrix(pivotRotationInvMatrix); + root.matrix.premultiply(pivotRotationInvMatrix); // Apply joystick translation - root.applyMatrix(move); + root.matrix.premultiply(move); // Apply joystick yaw rotation - root.applyMatrix(yawMatrix); + root.matrix.premultiply(yawMatrix); // Apply snap rotation if necessary - root.applyMatrix(this.pendingSnapRotationMatrix); + root.matrix.premultiply(this.pendingSnapRotationMatrix); // Reapply pivot (camera/head) rotation - root.applyMatrix(pivotRotationMatrix); + root.matrix.premultiply(pivotRotationMatrix); // Reapply playspace (player rig) rotation - root.applyMatrix(rotationMatrix); + root.matrix.premultiply(rotationMatrix); // Reapply playspace (player rig) translation - root.applyMatrix(trans); + root.matrix.premultiply(trans); + // update pos/rot/scale + root.matrix.decompose(root.position, root.quaternion, root.scale); // TODO: the above matrix trnsfomraitons introduce some floating point errors in scale, this reverts them to // avoid spamming network with fake scale updates diff --git a/src/components/cursor-controller.js b/src/components/cursor-controller.js index 83aea2193fd285c04cbda8946880134dc3d9120f..e0dda6f0bce57127c09ef5580bf84a497ad2caf8 100644 --- a/src/components/cursor-controller.js +++ b/src/components/cursor-controller.js @@ -28,8 +28,8 @@ AFRAME.registerComponent("cursor-controller", { this.wasCursorHovered = false; this.origin = new THREE.Vector3(); this.direction = new THREE.Vector3(); + this.raycasterAttr = this.el.getAttribute("raycaster"); this.controllerQuaternion = new THREE.Quaternion(); - this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered }); this._handleCursorLoaded = this._handleCursorLoaded.bind(this); @@ -45,6 +45,12 @@ AFRAME.registerComponent("cursor-controller", { this.setCursorVisibility(false); }, + updateRay: function() { + this.raycasterAttr.origin = this.origin; + this.raycasterAttr.direction = this.direction; + this.el.setAttribute("raycaster", this.raycasterAttr, true); + }, + tick: (() => { const rayObjectRotation = new THREE.Quaternion(); @@ -63,7 +69,7 @@ AFRAME.registerComponent("cursor-controller", { .applyQuaternion(rayObjectRotation) .normalize(); this.origin.setFromMatrixPosition(rayObject.matrixWorld); - this.el.setAttribute("raycaster", { origin: this.origin, direction: this.direction }); + this.updateRay(); } const isGrabbing = this.data.cursor.components["super-hands"].state.has("grab-start"); @@ -100,7 +106,7 @@ AFRAME.registerComponent("cursor-controller", { raycaster.setFromCamera(this.mousePos, camera); this.origin.copy(raycaster.ray.origin); this.direction.copy(raycaster.ray.direction); - this.el.setAttribute("raycaster", { origin: raycaster.ray.origin, direction: raycaster.ray.direction }); + this.updateRay(); }, updateDistanceAndTargetType: function() { diff --git a/src/components/look-on-mobile.js b/src/components/look-on-mobile.js index deb553e787f96a349d7b690901e5df9bb96ab892..7cde5ba136933c7d658693f00e3a3ea3a3316307 100644 --- a/src/components/look-on-mobile.js +++ b/src/components/look-on-mobile.js @@ -1,4 +1,3 @@ -const PolyfillControls = AFRAME.utils.device.PolyfillControls; const TWOPI = Math.PI * 2; class CircularBuffer { @@ -50,24 +49,23 @@ AFRAME.registerComponent("look-on-mobile", { init() { this.hmdEuler = new THREE.Euler(); + this.hmdQuaternion = new THREE.Quaternion(); this.prevX = this.hmdEuler.x; this.prevY = this.hmdEuler.y; this.pendingLookX = 0; this.onRotateX = this.onRotateX.bind(this); this.dXBuffer = new CircularBuffer(6); this.dYBuffer = new CircularBuffer(6); + this.vrDisplay = window.webvrpolyfill.getPolyfillDisplays()[0]; + this.frameData = new window.webvrpolyfill.constructor.VRFrameData(); }, play() { this.el.addEventListener("rotateX", this.onRotateX); - this.polyfillObject = new THREE.Object3D(); - this.polyfillControls = new PolyfillControls(this.polyfillObject); }, pause() { this.el.removeEventListener("rotateX", this.onRotateX); - this.polyfillControls = null; - this.polyfillObject = null; }, update() { @@ -81,8 +79,11 @@ AFRAME.registerComponent("look-on-mobile", { tick() { const hmdEuler = this.hmdEuler; const { horizontalLookSpeedRatio, verticalLookSpeedRatio } = this.data; - this.polyfillControls.update(); - hmdEuler.setFromQuaternion(this.polyfillObject.quaternion, "YXZ"); + this.vrDisplay.getFrameData(this.frameData); + if (this.frameData.pose.orientation !== null) { + this.hmdQuaternion.fromArray(this.frameData.pose.orientation); + hmdEuler.setFromQuaternion(this.hmdQuaternion, "YXZ"); + } const dX = THREE.Math.RAD2DEG * difference(hmdEuler.x, this.prevX); const dY = THREE.Math.RAD2DEG * difference(hmdEuler.y, this.prevY); diff --git a/src/hub.html b/src/hub.html index 9863b8361c91da6eaf2132597ade00307b14195b..a3b4cb48d94e1f517688677f9aa77128649dfc7e 100644 --- a/src/hub.html +++ b/src/hub.html @@ -13,12 +13,11 @@ <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet"> <% if(NODE_ENV === "production") { %> - <script src="https://cdn.rawgit.com/aframevr/aframe/3e7a4b3/dist/aframe-master.min.js" integrity="sha384-LQXa4VjhYucs9sVd5yQ3OhBXRea0jrvbHJA8CYLgTnvzxF5uvyhabSo1mX4tT2c6" crossorigin="anonymous"></script> + <script src="https://cdn.rawgit.com/aframevr/aframe/1be48d9/dist/aframe-master.min.js" integrity="sha384-SXrfoMHbXpA5RZhIyhgaR6tQ764dDZsbFk3PiokC/tc0+NnW1yaYQMUzWtL06hnq" crossorigin="anonymous"></script> <% } else { %> - <script src="https://cdn.rawgit.com/aframevr/aframe/3e7a4b3/dist/aframe-master.js" integrity="sha384-EaMOuyBOi9ERV/lVDwQgz/yFWBDWPsIju5Co6oCZZHXuvbLBO81yPWn80q0BbBn3" crossorigin="anonymous"></script> + <script src="https://cdn.rawgit.com/aframevr/aframe/1be48d9/dist/aframe-master.js" integrity="sha384-AmjDGOMbvTrrUFdeVWcBIlXRINIWnO8iwj/4VS21OWbYDsa/7nheOIyPAPJSkR6J" crossorigin="anonymous"></script> <% } %> - <!-- HACK: this has to run after A-Frame but before our bundle, since A-Frame blows away the local storage setting --> <script src="https://cdn.rawgit.com/gfodor/ba8f88d9f34fe9cbe59a01ce3c48420d/raw/03e31f0ef7b9eac5e947bd39e440f34df0701f75/naf-janus-adapter-logging.js" integrity="sha384-4q1V8Q88oeCFriFefFo5uEUtMzbw6K116tFyC9cwbiPr6wEe7050l5HoJUxMvnzj" crossorigin="anonymous"></script> </head> diff --git a/src/hub.js b/src/hub.js index 4c60dec0b56f8fe9707fb53449a7fe991c9e9f08..95133f739dc7c80750370dc685d6fbc923463ac3 100644 --- a/src/hub.js +++ b/src/hub.js @@ -224,7 +224,7 @@ const onReady = async () => { const scene = document.querySelector("a-scene"); if (scene) { if (scene.renderer) { - scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this + scene.renderer.setAnimationLoop(null); // Stop animation loop, TODO A-Frame should do this } document.body.removeChild(scene); } @@ -425,11 +425,11 @@ const onReady = async () => { if (!isBotMode) { // Stop rendering while the UI is up. We restart the render loop in enterScene. // Wait a tick plus some margin so that the environments actually render. - setTimeout(() => scene.renderer.animate(null), 100); + setTimeout(() => scene.renderer.setAnimationLoop(null), 100); } else { const noop = () => {}; // Replace renderer with a noop renderer to reduce bot resource usage. - scene.renderer = { animate: noop, render: noop }; + scene.renderer = { setAnimationLoop: noop, render: noop }; document.body.style.display = "none"; } }); diff --git a/src/network-schemas.js b/src/network-schemas.js index a1c91e93357c21267ba40a9d25d71d2bedfc475e..7addf6702740df5e67d4ebd0dd2f92849e91f07e 100644 --- a/src/network-schemas.js +++ b/src/network-schemas.js @@ -16,7 +16,6 @@ function registerNetworkSchemas() { }, { component: "rotation", - lerp: false, requiresNetworkUpdate: rotationRequiresUpdate }, "scale", diff --git a/src/react-components/entry-buttons.js b/src/react-components/entry-buttons.js index 9e8302b3c7bbf9bd44edbaf6538100f11abf3fb8..f2551df70f99813c5e74be91e7e627de3374feab 100644 --- a/src/react-components/entry-buttons.js +++ b/src/react-components/entry-buttons.js @@ -87,6 +87,17 @@ export const DaydreamEntryButton = props => { return <EntryButton {...entryButtonProps} />; }; +export const SafariEntryButton = props => { + const entryButtonProps = { + ...props, + iconSrc: MobileScreenEntryImg, + prefixMessageId: "entry.screen-prefix", + mediumMessageId: "entry.mobile-safari" + }; + + return <EntryButton {...entryButtonProps} />; +}; + export const DeviceEntryButton = props => { const entryButtonProps = { ...props, diff --git a/src/react-components/info-dialog.js b/src/react-components/info-dialog.js index 4b432e01cb5c04116e6c199d3a873eea8c2540c1..fe7c0e8df2419f751414bc05edcb003df64ce1b0 100644 --- a/src/react-components/info-dialog.js +++ b/src/react-components/info-dialog.js @@ -15,6 +15,7 @@ class InfoDialog extends Component { slack: Symbol("slack"), email_submitted: Symbol("email_submitted"), invite: Symbol("invite"), + safari: Symbol("safari"), updates: Symbol("updates"), report: Symbol("report"), help: Symbol("help"), @@ -74,8 +75,8 @@ class InfoDialog extends Component { }); }; - copyLinkClicked = () => { - copy(this.shareLink); + copyLinkClicked = link => { + copy(link); this.setState({ copyLinkButtonText: "Copied!" }); }; @@ -160,7 +161,35 @@ class InfoDialog extends Component { <span>Share</span> </button> )} - <button className="invite-form__action-button" onClick={this.copyLinkClicked}> + <button + className="invite-form__action-button" + onClick={this.copyLinkClicked.bind(this, this.shareLink)} + > + <span>{this.state.copyLinkButtonText}</span> + </button> + </div> + </div> + </div> + ); + break; + case InfoDialog.dialogTypes.safari: + dialogTitle = "Open in Safari"; + dialogBody = ( + <div> + <div>Hubs does not support your current browser on iOS. Copy and paste this link directly in Safari.</div> + <div className="invite-form"> + <input + type="text" + readOnly + onFocus={e => e.target.select()} + value={document.location} + className="invite-form__link_field" + /> + <div className="invite-form__buttons"> + <button + className="invite-form__action-button" + onClick={this.copyLinkClicked.bind(this, document.location)} + > <span>{this.state.copyLinkButtonText}</span> </button> </div> diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 5014d6af494e2f4ea551e8eb174c03b381db8c79..e5c56aa01147f9680c3ee6aa7fcf6e44658f4523 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -11,7 +11,13 @@ import screenfull from "screenfull"; import { lang, messages } from "../utils/i18n"; import AutoExitWarning from "./auto-exit-warning"; -import { TwoDEntryButton, DeviceEntryButton, GenericEntryButton, DaydreamEntryButton } from "./entry-buttons.js"; +import { + TwoDEntryButton, + DeviceEntryButton, + GenericEntryButton, + DaydreamEntryButton, + SafariEntryButton +} from "./entry-buttons.js"; import { ProfileInfoHeader } from "./profile-info-header.js"; import ProfileEntryPanel from "./profile-entry-panel"; import InfoDialog from "./info-dialog.js"; @@ -159,11 +165,11 @@ class UIRoot extends Component { handleForcedVREntryType = () => { if (!this.props.forcedVREntryType) return; - if (this.props.forcedVREntryType === "daydream") { + if (this.props.forcedVREntryType.startsWith("daydream")) { this.enterDaydream(); - } else if (this.props.forcedVREntryType === "vr") { + } else if (this.props.forcedVREntryType.startsWith("vr")) { this.enterVR(); - } else if (this.props.forcedVREntryType === "2d") { + } else if (this.props.forcedVREntryType.startsWith("2d")) { this.enter2D(); } }; @@ -250,7 +256,7 @@ class UIRoot extends Component { if (hasGrantedMic) { await this.setMediaStreamToDefault(); - this.beginAudioSetup(); + this.beginOrSkipAudioSetup(); } else { this.setState({ entryStep: ENTRY_STEPS.mic_grant }); } @@ -260,6 +266,10 @@ class UIRoot extends Component { await this.performDirectEntryFlow(false); }; + linkSafari = async () => { + this.setState({ infoDialogType: InfoDialog.dialogTypes.safari }); + }; + enterVR = async () => { if (this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.maybe) { await this.performDirectEntryFlow(true); @@ -411,10 +421,10 @@ class UIRoot extends Component { if (hasAudio) { this.setState({ entryStep: ENTRY_STEPS.mic_granted }); } else { - this.beginAudioSetup(); + this.beginOrSkipAudioSetup(); } } else { - this.beginAudioSetup(); + this.beginOrSkipAudioSetup(); } }; @@ -422,8 +432,12 @@ class UIRoot extends Component { this.setState({ showProfileEntry: false }); }; - beginAudioSetup = () => { - this.setState({ entryStep: ENTRY_STEPS.audio_setup }); + beginOrSkipAudioSetup = () => { + if (!this.props.forcedVREntryType || !this.props.forcedVREntryType.endsWith("_now")) { + this.setState({ entryStep: ENTRY_STEPS.audio_setup }); + } else { + setTimeout(this.onAudioReadyButton, 3000); // Need to wait otherwise input doesn't work :/ + } }; fetchMicDevices = () => { @@ -616,9 +630,12 @@ class UIRoot extends Component { this.state.entryStep === ENTRY_STEPS.start ? ( <div className="entry-panel"> <div className="entry-panel__button-container"> - {this.props.availableVREntryTypes.screen !== VR_DEVICE_AVAILABILITY.no && ( + {this.props.availableVREntryTypes.screen === VR_DEVICE_AVAILABILITY.yes && ( <TwoDEntryButton onClick={this.enter2D} /> )} + {this.props.availableVREntryTypes.safari === VR_DEVICE_AVAILABILITY.maybe && ( + <SafariEntryButton onClick={this.linkSafari} /> + )} {this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( <GenericEntryButton onClick={this.enterVR} /> )} diff --git a/src/systems/personal-space-bubble.js b/src/systems/personal-space-bubble.js index 0ba3cee5d48a1b5095031db89f9fa2cf6e490429..238c0b63f70c06502350b83dd354cbb256c755bf 100644 --- a/src/systems/personal-space-bubble.js +++ b/src/systems/personal-space-bubble.js @@ -74,13 +74,10 @@ AFRAME.registerSystem("personal-space-bubble", { tick() { if (!this.data.enabled) return; - // Update matrix positions once for each space bubble and space invader - for (let i = 0; i < this.bubbles.length; i++) { - this.bubbles[i].el.object3D.updateMatrixWorld(true); - } + // precondition for this stuff -- the bubbles and invaders need updated world matrices. + // right now this is satisfied because we update the world matrices in the character controller for (let i = 0; i < this.invaders.length; i++) { - this.invaders[i].el.object3D.updateMatrixWorld(true); this.invaders[i].setInvading(false); } diff --git a/src/utils/vr-caps-detect.js b/src/utils/vr-caps-detect.js index 224f1bb49ba60a0627808a73dc6c3139d7df8aac..8953f12a86f24e9848c08e3619d718a2b0eda615 100644 --- a/src/utils/vr-caps-detect.js +++ b/src/utils/vr-caps-detect.js @@ -57,8 +57,16 @@ export async function getAvailableVREntryTypes() { const isDaydreamCapableBrowser = !!(isWebVRCapableBrowser && browser.name === "chrome" && !isSamsungBrowser); const isIDevice = ["iPhone", "iPad", "iPod"].indexOf(deviceDetect.device) > -1; const isFirefoxBrowser = browser.name === "firefox"; + const isUIWebView = typeof navigator.mediaDevices === "undefined"; + + const safari = isIDevice + ? !isUIWebView ? VR_DEVICE_AVAILABILITY.yes : VR_DEVICE_AVAILABILITY.maybe + : VR_DEVICE_AVAILABILITY.no; + + const screen = isInHMD + ? VR_DEVICE_AVAILABILITY.no + : isIDevice && isUIWebView ? VR_DEVICE_AVAILABILITY.maybe : VR_DEVICE_AVAILABILITY.yes; - const screen = isInHMD ? VR_DEVICE_AVAILABILITY.no : VR_DEVICE_AVAILABILITY.yes; let generic = mobiledetect.mobile() ? VR_DEVICE_AVAILABILITY.no : VR_DEVICE_AVAILABILITY.maybe; let cardboard = VR_DEVICE_AVAILABILITY.no; @@ -107,5 +115,5 @@ export async function getAvailableVREntryTypes() { } } - return { screen, generic, gearvr, daydream, cardboard, isInHMD }; + return { screen, generic, gearvr, daydream, cardboard, isInHMD, safari }; } diff --git a/src/utils/webgl.js b/src/utils/webgl.js index 17b608b340d182da448ec549baafa2a90c838a45..503f96f431dce2b9ad3dfd0894d96ab90ebfd5be 100644 --- a/src/utils/webgl.js +++ b/src/utils/webgl.js @@ -16,20 +16,21 @@ function checkFloatTextureSupport() { renderer.dispose(); return result; } -const supportsFloatTextures = checkFloatTextureSupport(); export function patchWebGLRenderingContext() { - const originalGetExtension = WebGLRenderingContext.prototype.getExtension; - function patchedGetExtension(name) { + if (/Android.+Firefox/.test(navigator.userAgent)) { // It appears that Galaxy S6 devices falsely report that they support // OES_texture_float in Firefox. This workaround disables float textures // for those devices. // See https://github.com/mozilla/hubs/issues/32 and // https://bugzilla.mozilla.org/show_bug.cgi?id=1338656 - if (name === "OES_texture_float" && /Android.+Firefox/.test(navigator.userAgent) && !supportsFloatTextures) { - return null; - } - return originalGetExtension.call(this, name); + const originalGetExtension = WebGLRenderingContext.prototype.getExtension; + const supportsFloatTextures = checkFloatTextureSupport(); + WebGLRenderingContext.prototype.getExtension = function patchedGetExtension(name) { + if (name === "OES_texture_float" && !supportsFloatTextures) { + return null; + } + return originalGetExtension.call(this, name); + }; } - WebGLRenderingContext.prototype.getExtension = patchedGetExtension; } diff --git a/webpack.config.js b/webpack.config.js index 17fbb4f8b666a03a7d1577f07d011040b6125f40..6f66a85c9c59fb1cbd5a339519af6eec23987eb7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -41,6 +41,10 @@ function createHTTPSConfig() { { type: 2, value: "localhost" + }, + { + type: 2, + value: "hubs.local" } ] } @@ -93,6 +97,7 @@ const config = { https: createHTTPSConfig(), host: "0.0.0.0", useLocalIp: true, + public: "hubs.local:8080", port: 8080, before: function(app) { // networked-aframe makes HEAD requests to the server for time syncing. Respond with an empty body. diff --git a/yarn.lock b/yarn.lock index 906c4615fdbd1f5f97e331043fbf27350f45799d..589589dc64406716bd1084f40581f5ad2b410af0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1643,6 +1643,10 @@ buffer@^5.0.2: base64-js "^1.0.2" ieee754 "^1.1.4" +buffered-interpolation@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/buffered-interpolation/-/buffered-interpolation-0.2.3.tgz#6e723d44c4f4aa76704fc470654174e279591c31" + builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -2036,8 +2040,8 @@ colormin@^1.0.5: has "^1.0.1" colors@*: - version "1.2.5" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" + version "1.3.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.0.tgz#5f20c9fef6945cb1134260aab33bfbdc8295e04e" colors@1.0.3: version "1.0.3" @@ -5511,8 +5515,9 @@ neo-async@^2.5.0: "networked-aframe@https://github.com/mozillareality/networked-aframe#mr-social-client/master": version "0.6.1" - resolved "https://github.com/mozillareality/networked-aframe#424b41cfdf53db64033885da411c33685644db97" + resolved "https://github.com/mozillareality/networked-aframe#7b88e49e855b60e376886abe23ea311b27acdffe" dependencies: + buffered-interpolation "^0.2.3" easyrtc "1.1.0" express "^4.10.7" serve-static "^1.8.0"