From 2915a62c2ecaea0aa6ff27b85f6d371816629704 Mon Sep 17 00:00:00 2001 From: joni <johnfshaughnessy@gmail.com> Date: Thu, 10 May 2018 17:53:26 -0700 Subject: [PATCH] Merge with master. Switch back to touch events. --- PRIVACY.md | 2 - package.json | 2 +- scripts/default.env | 2 +- src/assets/stylesheets/index.scss | 2 +- src/assets/stylesheets/info-dialog.scss | 14 ++- src/components/character-controller.js | 18 ++-- src/components/cursor-controller.js | 124 +++++++++++++----------- src/components/in-world-hud.js | 9 +- src/components/stats-plus.js | 4 +- src/hub.html | 58 +++++------ src/hub.js | 25 +++-- src/react-components/home-root.js | 31 +++++- src/react-components/info-dialog.js | 36 ++++++- src/storage/store.js | 2 +- src/utils/audio-context-fix.js | 22 +++++ src/utils/ios-audio-context-fix.js | 23 ----- src/utils/pinch-to-move.js | 16 +-- src/utils/pinch.js | 100 +++++++++++++------ src/utils/pointer-look-controls.js | 103 ++++++++++++-------- yarn.lock | 4 +- 20 files changed, 371 insertions(+), 226 deletions(-) create mode 100644 src/utils/audio-context-fix.js delete mode 100644 src/utils/ios-audio-context-fix.js diff --git a/PRIVACY.md b/PRIVACY.md index 4b0acc3e1..89afb36c0 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -19,8 +19,6 @@ In this Privacy Notice, we explain what data may be accessible to Mozilla or oth - You can learn more by looking at the code itself. [Janus SFU](https://github.com/mozilla/janus-plugin-sfu), [Reticulum](https://github.com/mozilla/reticulum), [Hubs](https://github.com/mozilla/hubs), [Hubs-Ops](https://github.com/mozilla/hubs-ops) </details> -<p/> - <details open> <summary> <strong>Mozilla receives technical and interaction data to improve performance and stability.</strong> diff --git a/package.json b/package.json index 16cb3510f..f51fa7aed 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "aframe-extras": "https://github.com/MozillaReality/aframe-extras#feature/precompute-nav-mesh", "aframe-input-mapping-component": "https://github.com/mozillareality/aframe-input-mapping-component#hubs/master", "aframe-physics-extras": "^0.1.3", - "aframe-physics-system": "^3.1.1", + "aframe-physics-system": "github:infinitelee/aframe-physics-system#hubs/master", "aframe-rounded": "^1.0.3", "aframe-slice9-component": "^1.0.0", "aframe-teleport-controls": "https://github.com/mozillareality/aframe-teleport-controls#hubs/master", diff --git a/scripts/default.env b/scripts/default.env index 3d6556c0f..72a793ee3 100644 --- a/scripts/default.env +++ b/scripts/default.env @@ -1,7 +1,7 @@ # This origin trial token is used to enable WebVR and Gamepad Extensions on Chrome 62+ # You can find more information about getting your own origin trial token here: https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md ORIGIN_TRIAL_TOKEN="AgN/JtqSF6qpD3OZk8KgM5/UYqUUrwc166cOQSRCqvU+TIpHWdiwBUWH5V1K/jJkdtBrO4Q5I0XSGm16uB/Y4QQAAABVeyJvcmlnaW4iOiJodHRwczovL2h1YnMubW96aWxsYS5jb206NDQzIiwiZmVhdHVyZSI6IldlYlZSMS4xTTYyIiwiZXhwaXJ5IjoxNTI4MjQ1ODI1fQ==" -ORIGIN_TRIAL_EXPIRES="2018-05-15" +ORIGIN_TRIAL_EXPIRES="2018-06-05" JANUS_SERVER="wss://prod-janus.reticulum.io" DEV_RETICULUM_SERVER="dev.reticulum.io" ASSET_BUNDLE_SERVER="https://asset-bundles-prod.reticulum.io" diff --git a/src/assets/stylesheets/index.scss b/src/assets/stylesheets/index.scss index 7aa0079ce..014ca0a7a 100644 --- a/src/assets/stylesheets/index.scss +++ b/src/assets/stylesheets/index.scss @@ -45,7 +45,7 @@ body { position: fixed; top: 0; left: 0; - opacity: 0.66; + opacity: 0.45; min-width: 100%; min-height: 100%; z-index: 1; diff --git a/src/assets/stylesheets/info-dialog.scss b/src/assets/stylesheets/info-dialog.scss index ae46af24e..47b06a436 100644 --- a/src/assets/stylesheets/info-dialog.scss +++ b/src/assets/stylesheets/info-dialog.scss @@ -3,8 +3,7 @@ height: 100%; top: 0; left: 0; - position: absolute; - pointer-events: none; + position: fixed; color: white; z-index: 2; } @@ -54,6 +53,17 @@ a { color: white } } + &__links { + display: flex; + justify-content: center; + margin: 16px 0; + a { + margin: 0 12px; + color: $light-text; + } + } + + &__close { position: absolute; left: 12px; diff --git a/src/components/character-controller.js b/src/components/character-controller.js index 26dc12a8c..174148d9c 100644 --- a/src/components/character-controller.js +++ b/src/components/character-controller.js @@ -75,7 +75,7 @@ AFRAME.registerComponent("character-controller", { }, handleTeleport: function(event) { - this.setPositionOnNavMesh(event.detail.oldPosition, this.el.object3D); + this.setPositionOnNavMesh(event.detail.oldPosition, event.detail.newPosition, this.el.object3D, true); }, tick: (function() { @@ -142,19 +142,23 @@ AFRAME.registerComponent("character-controller", { this.pendingSnapRotationMatrix.identity(); // Revert to identity if (this.velocity.lengthSq() > EPS) { - this.setPositionOnNavMesh(startPos, root); + this.setPositionOnNavMesh(startPos, root.position, root); } }; })(), - setPositionOnNavMesh: function(position, object3D) { + setPositionOnNavMesh: function(startPosition, endPosition, object3D, resetPosition = false) { const nav = this.el.sceneEl.systems.nav; if (nav.navMesh) { - if (!this.navGroup) { - this.navGroup = nav.getGroup(position); + if (!this.navGroup || resetPosition) { + this.navGroup = nav.getGroup(endPosition); } - this.navNode = this.navNode || nav.getNode(position, this.navGroup); - this.navNode = nav.clampStep(position, object3D.position, this.navGroup, this.navNode, object3D.position); + + if (!this.navNode || resetPosition) { + this.navNode = nav.getNode(endPosition, this.navGroup) || this.navNode; + } + + this.navNode = nav.clampStep(startPosition, endPosition, this.navGroup, this.navNode, object3D.position); } }, diff --git a/src/components/cursor-controller.js b/src/components/cursor-controller.js index 7b2c16f77..debcd20ca 100644 --- a/src/components/cursor-controller.js +++ b/src/components/cursor-controller.js @@ -42,26 +42,22 @@ AFRAME.registerComponent("cursor-controller", { this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered }); - const functionNames = [ - "_handlePointerDown", - "_handlePointerMove", - "_handlePointerUp", - "_handleMouseDown", - "_handleMouseMove", - "_handleMouseUp", - "_handleWheel", - "_handleEnterVR", - "_handleExitVR", - "_handlePrimaryDown", - "_handlePrimaryUp", - "_handleModelLoaded", - "_handleCursorLoaded", - "_handleControllerConnected", - "_handleControllerDisconnected" - ]; - functionNames.forEach(name => { - this[name] = this[name].bind(this); - }); + this._handleTouchStart = this._handleTouchStart.bind(this); + this._handleSingleTouchStart = this._handleSingleTouchStart.bind(this); + this._handleTouchMove = this._handleTouchMove.bind(this); + this._handleTouchEnd = this._handleTouchEnd.bind(this); + this._handleMouseDown = this._handleMouseDown.bind(this); + this._handleMouseMove = this._handleMouseMove.bind(this); + this._handleMouseUp = this._handleMouseUp.bind(this); + this._handleWheel = this._handleWheel.bind(this); + this._handleEnterVR = this._handleEnterVR.bind(this); + this._handleExitVR = this._handleExitVR.bind(this); + this._handlePrimaryDown = this._handlePrimaryDown.bind(this); + this._handlePrimaryUp = this._handlePrimaryUp.bind(this); + this._handleModelLoaded = this._handleModelLoaded.bind(this); + this._handleCursorLoaded = this._handleCursorLoaded.bind(this); + this._handleControllerConnected = this._handleControllerConnected.bind(this); + this._handleControllerDisconnected = this._handleControllerDisconnected.bind(this); this.data.cursor.addEventListener("loaded", this._handleCursorLoaded); }, @@ -81,10 +77,10 @@ AFRAME.registerComponent("cursor-controller", { }, play: function() { - document.addEventListener("pointerdown", this._handlePointerDown); - document.addEventListener("pointermove", this._handlePointerMove); - document.addEventListener("pointerup", this._handlePointerUp); - document.addEventListener("pointercancel", this._handlePointerUp); + document.addEventListener("touchstart", this._handleTouchStart); + document.addEventListener("touchmove", this._handleTouchMove); + document.addEventListener("touchend", this._handleTouchEnd); + document.addEventListener("touchcancel", this._handleTouchEnd); document.addEventListener("mousedown", this._handleMouseDown); document.addEventListener("mousemove", this._handleMouseMove); document.addEventListener("mouseup", this._handleMouseUp); @@ -97,6 +93,8 @@ AFRAME.registerComponent("cursor-controller", { this.data.playerRig.addEventListener(this.data.primaryUp, this._handlePrimaryUp); this.data.playerRig.addEventListener(this.data.grabEvent, this._handlePrimaryDown); this.data.playerRig.addEventListener(this.data.releaseEvent, this._handlePrimaryUp); + this.data.playerRig.addEventListener("gamepadbuttondown", this._handlePrimaryDown); + this.data.playerRig.addEventListener("gamepadbuttonup", this._handlePrimaryUp); this.data.playerRig.addEventListener("model-loaded", this._handleModelLoaded); this.el.sceneEl.addEventListener("controllerconnected", this._handleControllerConnected); @@ -104,10 +102,10 @@ AFRAME.registerComponent("cursor-controller", { }, pause: function() { - document.removeEventListener("pointerdown", this._handlePointerDown); - document.removeEventListener("pointermove", this._handlePointerMove); - document.removeEventListener("pointerup", this._handlePointerUp); - document.removeEventListener("pointercancel", this._handlePointerUp); + document.removeEventListener("touchstart", this._handleTouchStart); + document.removeEventListener("touchmove", this._handleTouchMove); + document.removeEventListener("touchend", this._handleTouchEnd); + document.removeEventListener("touchcancel", this._handleTouchEnd); document.removeEventListener("mousedown", this._handleMouseDown); document.removeEventListener("mousemove", this._handleMouseMove); document.removeEventListener("mouseup", this._handleMouseUp); @@ -120,6 +118,8 @@ AFRAME.registerComponent("cursor-controller", { this.data.playerRig.removeEventListener(this.data.primaryUp, this._handlePrimaryUp); this.data.playerRig.removeEventListener(this.data.grabEvent, this._handlePrimaryDown); this.data.playerRig.removeEventListener(this.data.releaseEvent, this._handlePrimaryUp); + this.data.playerRig.removeEventListener("gamepadbuttondown", this._handlePrimaryDown); + this.data.playerRig.removeEventListener("gamepadbuttonup", this._handlePrimaryUp); this.data.playerRig.removeEventListener("model-loaded", this._handleModelLoaded); this.el.sceneEl.removeEventListener("controllerconnected", this._handleControllerConnected); @@ -229,7 +229,9 @@ AFRAME.registerComponent("cursor-controller", { }, _setLookControlsEnabled(enabled) { - window.LookControlsToggle.toggle(enabled, this); + if (window.LookControlsToggle) { + window.LookControlsToggle.toggle(enabled, this); + } }, _startTeleport: function() { @@ -250,28 +252,33 @@ AFRAME.registerComponent("cursor-controller", { this._setCursorVisibility(true); }, - _handlePointerDown: function(e) { - if (!this.isMobile || this.hasPointingDevice || this.activeTouch || e.clientY / window.innerHeight >= 0.8) return; + _handleTouchStart: function(e) { + if (!this.isMobile || this.hasPointingDevice) { + return; + } - this.activeTouch = e; + for (let i = 0; i < e.touches.length; i++) { + this._handleSingleTouchStart(e.touches[i]); + } + }, + + _handleSingleTouchStart: function(touch) { + if (this.activeTouch || touch.clientY / window.innerHeight >= 0.8) return; // Update the ray and cursor positions const raycasterComp = this.el.components.raycaster; const raycaster = raycasterComp.raycaster; const camera = this.data.camera.components.camera.camera; const cursor = this.data.cursor; - this.mousePos.set( - this.activeTouch.clientX / window.innerWidth * 2 - 1, - -(this.activeTouch.clientY / window.innerHeight) * 2 + 1 - ); + this.mousePos.set(touch.clientX / window.innerWidth * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1); raycaster.setFromCamera(this.mousePos, camera); this.el.setAttribute("raycaster", { origin: raycaster.ray.origin, direction: raycaster.ray.direction }); raycasterComp.checkIntersections(); const intersections = raycasterComp.intersections; if (intersections.length === 0 || intersections[0].distance >= this.data.maxDistance) { - this.activeTouch = null; return; } + this.activeTouch = touch; cursor.object3D.position.copy(intersections[0].point); // Cursor position must be synced to physics before constraint is created cursor.components["static-body"].syncToPhysics(); @@ -280,20 +287,28 @@ AFRAME.registerComponent("cursor-controller", { cursor.emit("cursor-grab", {}); }, - _handlePointerMove: function(e) { + _handleTouchMove: function(e) { if (!this.isMobile || this.hasPointingDevice) return; - if ( - (!this.activeTouch && e.clientY / window.innerHeight < 0.8) || - (this.activeTouch && e.pointerId === this.activeTouch.pointerId) - ) { - this.mousePos.set(e.clientX / window.innerWidth * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1); - return; + for (let i = 0; i < e.touches.length; i++) { + const touch = e.touches[i]; + if ( + (!this.activeTouch && touch.clientY / window.innerHeight < 0.8) || + (this.activeTouch && touch.identifier === this.activeTouch.identifier) + ) { + this.mousePos.set(touch.clientX / window.innerWidth * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1); + return; + } } }, - _handlePointerUp: function(e) { - if (!this.isMobile || this.hasPointingDevice || !this.activeTouch || e.pointerId !== this.activeTouch.pointerId) { + _handleTouchEnd: function(e) { + if ( + !this.isMobile || + this.hasPointingDevice || + !this.activeTouch || + Array.prototype.some.call(e.touches, touch => touch.identifier === this.activeTouch.identifier) + ) { return; } @@ -343,10 +358,8 @@ AFRAME.registerComponent("cursor-controller", { }, _handleEnterVR: function() { - if (AFRAME.utils.device.checkHeadsetConnected()) { - this.inVR = true; - this._updateController(); - } + this.inVR = true; + this._updateController(); }, _handleExitVR: function() { @@ -382,7 +395,7 @@ AFRAME.registerComponent("cursor-controller", { }, _handleCursorLoaded: function() { - this.data.cursor.object3DMap.mesh.renderOrder = window.RENDER_ORDER.CURSOR; + this.data.cursor.object3DMap.mesh.renderOrder = window.APP.RENDER_ORDER.CURSOR; }, _handleControllerConnected: function(e) { @@ -413,7 +426,7 @@ AFRAME.registerComponent("cursor-controller", { _updateController: function() { this.hasPointingDevice = this.controllerQueue.length > 0 && this.inVR; - this._setCursorVisibility(this.hasPointingDevice); + this._setCursorVisibility(this.hasPointingDevice || this.isMobile); if (this.hasPointingDevice) { const controllerData = this.controllerQueue[0]; @@ -423,12 +436,5 @@ AFRAME.registerComponent("cursor-controller", { } else { this.controller = null; } - }, - - some: function(a, fn) { - for (let i = 0; i < a.length; a++) { - if (fn(a[i])) return true; - } - return false; } }); diff --git a/src/components/in-world-hud.js b/src/components/in-world-hud.js index 30a912ef9..7633414b6 100644 --- a/src/components/in-world-hud.js +++ b/src/components/in-world-hud.js @@ -8,10 +8,11 @@ AFRAME.registerComponent("in-world-hud", { this.freeze = this.el.querySelector(".freeze"); this.bubble = this.el.querySelector(".bubble"); this.background = this.el.querySelector(".bg"); - this.mic.object3DMap.mesh.renderOrder = window.RENDER_ORDER.HUD; - this.freeze.object3DMap.mesh.renderOrder = window.RENDER_ORDER.HUD; - this.bubble.object3DMap.mesh.renderOrder = window.RENDER_ORDER.HUD; - this.background.object3DMap.mesh.renderORder = window.RENDER_ORDER.HUD_BACKGROUND; + const renderOrder = window.APP.RENDER_ORDER; + this.mic.object3DMap.mesh.renderOrder = renderOrder.HUD; + this.freeze.object3DMap.mesh.renderOrder = renderOrder.HUD; + this.bubble.object3DMap.mesh.renderOrder = renderOrder.HUD; + this.background.object3DMap.mesh.renderORder = renderOrder.HUD_BACKGROUND; this.updateButtonStates = () => { this.mic.setAttribute("icon-button", "active", this.el.sceneEl.is("muted")); diff --git a/src/components/stats-plus.js b/src/components/stats-plus.js index 2c4c14800..6229b5f33 100644 --- a/src/components/stats-plus.js +++ b/src/components/stats-plus.js @@ -123,8 +123,8 @@ AFRAME.registerComponent("stats-plus", { this.el.setAttribute(this.name, false); }, remove() { - this.el.sceneEl.removeListener("enter-vr", this.hide); - this.el.sceneEl.removeListener("exit-vr", this.show); + this.el.sceneEl.removeEventListener("enter-vr", this.hide); + this.el.sceneEl.removeEventListener("exit-vr", this.show); if (this.statsEl) { this.statsEl.parentNode.removeChild(this.statsEl); diff --git a/src/hub.html b/src/hub.html index 4d0036bcc..c8771f313 100644 --- a/src/hub.html +++ b/src/hub.html @@ -38,19 +38,19 @@ > <a-assets> - <img id="tooltip" src="./assets/hud/tooltip.9.png" > - <img id="mute-off" src="./assets/hud/mute_off.png" > - <img id="mute-off-hover" src="./assets/hud/mute_off-hover.png" > - <img id="mute-on" src="./assets/hud/mute_on.png" > - <img id="mute-on-hover" src="./assets/hud/mute_on-hover.png" > - <img id="bubble-off" src="./assets/hud/bubble_off.png" > - <img id="bubble-off-hover" src="./assets/hud/bubble_off-hover.png" > - <img id="bubble-on" src="./assets/hud/bubble_on.png" > - <img id="bubble-on-hover" src="./assets/hud/bubble_on-hover.png" > - <img id="freeze-off" src="./assets/hud/freeze_off.png" > - <img id="freeze-off-hover" src="./assets/hud/freeze_off-hover.png" > - <img id="freeze-on" src="./assets/hud/freeze_on.png" > - <img id="freeze-on-hover" src="./assets/hud/freeze_on-hover.png" > + <img id="tooltip" crossorigin="anonymous" src="./assets/hud/tooltip.9.png"> + <img id="mute-off" crossorigin="anonymous" src="./assets/hud/mute_off.png"> + <img id="mute-off-hover" crossorigin="anonymous" src="./assets/hud/mute_off-hover.png"> + <img id="mute-on" crossorigin="anonymous" src="./assets/hud/mute_on.png"> + <img id="mute-on-hover" crossorigin="anonymous" src="./assets/hud/mute_on-hover.png"> + <img id="bubble-off" crossorigin="anonymous" src="./assets/hud/bubble_off.png"> + <img id="bubble-off-hover" crossorigin="anonymous" src="./assets/hud/bubble_off-hover.png"> + <img id="bubble-on" crossorigin="anonymous" src="./assets/hud/bubble_on.png"> + <img id="bubble-on-hover" crossorigin="anonymous" src="./assets/hud/bubble_on-hover.png"> + <img id="freeze-off" crossorigin="anonymous" src="./assets/hud/freeze_off.png"> + <img id="freeze-off-hover" crossorigin="anonymous" src="./assets/hud/freeze_off-hover.png"> + <img id="freeze-on" crossorigin="anonymous" src="./assets/hud/freeze_on.png"> + <img id="freeze-on-hover" crossorigin="anonymous" src="./assets/hud/freeze_on-hover.png"> <a-asset-item id="botdefault" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotDefault_Avatar-9f71f8ff22.gltf"></a-asset-item> <a-asset-item id="botbobo" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotBobo_Avatar-f9740a010b.gltf"></a-asset-item> @@ -69,7 +69,7 @@ <a-asset-item id="quack" src="./assets/sfx/quack.mp3" response-type="arraybuffer" preload="auto"></a-asset-item> <a-asset-item id="specialquack" src="./assets/sfx/specialquack.mp3" response-type="arraybuffer" preload="auto"></a-asset-item> - <img id="water-normal-map" src="./assets/waternormals.jpg"> + <img id="water-normal-map" crossorigin="anonymous" src="./assets/waternormals.jpg"> <!-- Templates --> @@ -166,7 +166,7 @@ <template id="interactable-template"> <a-entity gltf-model-plus="src: #interactable-duck; inflate: true;" - class="interactable" + class="interactable" super-networked-interactable="counter: #counter; mass: 1;" body="type: dynamic; shape: none; mass: 1;" grabbable @@ -194,8 +194,8 @@ <a-entity id="cursor-controller" cursor-controller=" - cursor: #cursor; - camera: #player-camera; + cursor: #cursor; + camera: #player-camera; playerRig: #player-rig; physicalHandSelector: #player-right-controller; gazeTeleportControls: #gaze-teleport;" @@ -218,7 +218,7 @@ segments-height="9" segments-width="9" event-repeater="events: raycaster-intersection, raycaster-intersection-cleared; eventSource: #cursor-controller" - ></a-sphere> + ></a-sphere> <!-- Player Rig --> <a-entity @@ -259,9 +259,9 @@ id="gaze-teleport" position = "0.15 0 0" teleport-controls=" - cameraRig: #player-rig; - teleportOrigin: #player-camera; - button: cursor-teleport_; + cameraRig: #player-rig; + teleportOrigin: #player-camera; + button: cursor-teleport_; collisionEntities: [nav-mesh]; drawIncrementally: true; incrementalDrawMs: 600; @@ -276,9 +276,9 @@ hand-controls2="left" tracked-controls teleport-controls=" - cameraRig: #player-rig; - teleportOrigin: #player-camera; - button: cursor-teleport_; + cameraRig: #player-rig; + teleportOrigin: #player-camera; + button: cursor-teleport_; collisionEntities: [nav-mesh]; drawIncrementally: true; incrementalDrawMs: 600; @@ -297,9 +297,9 @@ hand-controls2="right" tracked-controls teleport-controls=" - cameraRig: #player-rig; - teleportOrigin: #player-camera; - button: cursor-teleport_; + cameraRig: #player-rig; + teleportOrigin: #player-camera; + button: cursor-teleport_; collisionEntities: [nav-mesh]; drawIncrementally: true; incrementalDrawMs: 600; @@ -346,8 +346,8 @@ </a-entity> <!-- Environment --> - <a-entity - id="environment-root" + <a-entity + id="environment-root" nav-mesh-helper static-body="shape: none;" ></a-entity> diff --git a/src/hub.js b/src/hub.js index e7b05cc58..aea22f113 100644 --- a/src/hub.js +++ b/src/hub.js @@ -17,7 +17,7 @@ import "aframe-billboard-component"; import "aframe-rounded"; import "webrtc-adapter"; import "aframe-slice9-component"; -import "./utils/ios-audio-context-fix"; +import "./utils/audio-context-fix"; import trackpad_dpad4 from "./behaviours/trackpad-dpad4"; import joystick_dpad4 from "./behaviours/joystick-dpad4"; @@ -86,6 +86,11 @@ if (qs.quality) { } else { window.APP.quality = isMobile ? "low" : "high"; } +window.APP.RENDER_ORDER = { + HUD_BACKGROUND: 1, + HUD: 2, + CURSOR: 3 +}; import "aframe-physics-system"; import "aframe-physics-extras"; @@ -115,12 +120,6 @@ import PinchToMove from "./utils/pinch-to-move.js"; import LookControlsToggle from "./utils/look-controls-toggle.js"; import PointerLookControls from "./utils/pointer-look-controls.js"; -window.RENDER_ORDER = { - HUD_BACKGROUND: 1, - HUD: 2, - CURSOR: 3 -}; - function qsTruthy(param) { const val = qs[param]; // if the param exists but is not set (e.g. "?foo&bar"), its value is null. @@ -205,10 +204,16 @@ const onReady = async () => { if (NAF.connection.adapter && NAF.connection.adapter.localMediaStream) { NAF.connection.adapter.localMediaStream.getTracks().forEach(t => t.stop()); } - hubChannel.disconnect(); + if (hubChannel) { + hubChannel.disconnect(); + } const scene = document.querySelector("a-scene"); - scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this - document.body.removeChild(scene); + if (scene) { + if (scene.renderer) { + scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this + } + document.body.removeChild(scene); + } }; const enterScene = async (mediaStream, enterInVR, janusRoomId) => { diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js index 63353a411..df85b4256 100644 --- a/src/react-components/home-root.js +++ b/src/react-components/home-root.js @@ -30,9 +30,36 @@ class HomeRoot extends Component { componentDidMount() { this.loadEnvironments(); this.setState({ dialogType: this.props.dialogType }); - document.querySelector("#background-video").playbackRate = 0.75; + this.loadHomeVideo(); } + loadHomeVideo = () => { + const videoEl = document.querySelector("#background-video"); + function initVideo() { + videoEl.playbackRate = 0.75; + videoEl.play(); + function toggleVideo() { + // Play the video if the window/tab is visible. + if (!("hasFocus" in document)) { + return; + } + if (document.hasFocus()) { + videoEl.play(); + } else { + videoEl.pause(); + } + } + document.addEventListener("visibilitychange", toggleVideo); + window.addEventListener("focus", toggleVideo); + window.addEventListener("blur", toggleVideo); + } + if (videoEl.readyState >= videoEl.HAVE_FUTURE_DATA) { + initVideo(); + } else { + videoEl.addEventListener("canplay", initVideo); + } + }; + showDialog = dialogType => { return e => { e.preventDefault(); @@ -181,7 +208,7 @@ class HomeRoot extends Component { </div> </div> </div> - <video playsInline autoPlay muted loop className="background-video" id="background-video"> + <video playsInline muted loop className="background-video" id="background-video"> <source src={homeVideoWebM} type="video/webm" /> <source src={homeVideoMp4} type="video/mp4" /> </video> diff --git a/src/react-components/info-dialog.js b/src/react-components/info-dialog.js index ab7bdda1c..6e27e39c9 100644 --- a/src/react-components/info-dialog.js +++ b/src/react-components/info-dialog.js @@ -27,6 +27,28 @@ class InfoDialog extends Component { const loc = document.location; this.shareLink = `${loc.protocol}//${loc.host}${loc.pathname}`; + this.onKeyDown = this.onKeyDown.bind(this); + this.onContainerClicked = this.onContainerClicked.bind(this); + } + + componentDidMount() { + window.addEventListener("keydown", this.onKeyDown); + } + + componentWillUnmount() { + window.removeEventListener("keydown", this.onKeyDown); + } + + onKeyDown(e) { + if (e.key === "Escape") { + this.props.onCloseDialog(); + } + } + + onContainerClicked(e) { + if (e.currentTarget === e.target) { + this.props.onCloseDialog(); + } } shareLinkClicked = () => { @@ -211,6 +233,18 @@ class InfoDialog extends Component { <p> The <b>Bubble Toggle</b> hides avatars that enter your personal space. </p> + <p className="dialog__box__contents__links"> + <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> + <FormattedMessage id="profile.terms_of_use" /> + </a> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" + > + <FormattedMessage id="profile.privacy_notice" /> + </a> + </p> </div> ); break; @@ -223,7 +257,7 @@ class InfoDialog extends Component { return ( <div className="dialog-overlay"> - <div className={dialogClasses}> + <div className={dialogClasses} onClick={this.onContainerClicked}> <div className="dialog__box"> <div className="dialog__box__contents"> <button className="dialog__box__contents__close" onClick={this.props.onCloseDialog}> diff --git a/src/storage/store.js b/src/storage/store.js index ce44da771..23f316819 100644 --- a/src/storage/store.js +++ b/src/storage/store.js @@ -1,5 +1,5 @@ import { Validator } from "jsonschema"; -import { merge } from "lodash"; +import merge from "lodash/merge"; const LOCAL_STORE_KEY = "___hubs_store"; const STORE_STATE_CACHE_KEY = Symbol(); diff --git a/src/utils/audio-context-fix.js b/src/utils/audio-context-fix.js new file mode 100644 index 000000000..1d3df3b08 --- /dev/null +++ b/src/utils/audio-context-fix.js @@ -0,0 +1,22 @@ +/** + * Chrome and Safari will start Audio contexts in a "suspended" state. + * A user interaction (touch/mouse event) is needed in order to resume the AudioContext. + */ + +document.addEventListener("DOMContentLoaded", () => { + const ctx = THREE.AudioContext.getContext(); + + function resume() { + ctx.resume(); + + setTimeout(function() { + if (ctx.state === "running") { + document.body.removeEventListener("touchend", resume, false); + document.body.removeEventListener("mouseup", resume, false); + } + }, 0); + } + + document.body.addEventListener("touchend", resume, false); + document.body.addEventListener("mouseup", resume, false); +}); diff --git a/src/utils/ios-audio-context-fix.js b/src/utils/ios-audio-context-fix.js deleted file mode 100644 index ce474194f..000000000 --- a/src/utils/ios-audio-context-fix.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Mobile Safari will start Audio contexts in a "suspended" state. - * A user interaction (touch event) is needed in order to resume the AudioContext. - */ -const iDevices = /\biPhone.*Mobile|\biPod|\biPad|AppleCoreMedia/; - -if (iDevices.test(navigator.userAgent)) { - document.addEventListener("DOMContentLoaded", () => { - const ctx = THREE.AudioContext.getContext(); - - function resume() { - ctx.resume(); - - setTimeout(function() { - if (ctx.state === "running") { - document.body.removeEventListener("touchend", resume, false); - } - }, 0); - } - - document.body.addEventListener("touchend", resume, false); - }); -} diff --git a/src/utils/pinch-to-move.js b/src/utils/pinch-to-move.js index 093fc9ac7..af474d5c4 100644 --- a/src/utils/pinch-to-move.js +++ b/src/utils/pinch-to-move.js @@ -20,26 +20,28 @@ export default class PinchToMove { } this.el.emit("move", { axis: [0, this.dir * this.decayingSpeed] }); - this.decayingSpeed *= 0.93; + this.decayingSpeed *= 0.95; } onPinch(e) { const dist = e.detail.distance * this.speed; this.decayingSpeed = dist; this.dir = -1; + this.el.emit("move", { axis: [0, this.dir * dist] }); - if (!this.interval) { - this.interval = window.setInterval(this.decay, 20); - } + // if (!this.interval) { + // this.interval = window.setInterval(this.decay, 20); + // } } onSpread(e) { const dist = e.detail.distance * this.speed; this.decayingSpeed = dist; this.dir = 1; + this.el.emit("move", { axis: [0, this.dir * dist] }); - if (!this.interval) { - this.interval = window.setInterval(this.decay, 20); - } + // if (!this.interval) { + // this.interval = window.setInterval(this.decay, 20); + // } } } diff --git a/src/utils/pinch.js b/src/utils/pinch.js index cbb2ba503..df28f571b 100644 --- a/src/utils/pinch.js +++ b/src/utils/pinch.js @@ -2,49 +2,62 @@ export default class Pinch { constructor(el) { this.el = el; this.prevDiff = -1; - this.evCache = []; + this.touchCache = []; + this.usedTouch = { identifier: -1 }; - this.onPointerMove = this.onPointerMove.bind(this); - this.onPointerDown = this.onPointerDown.bind(this); - this.onPointerUp = this.onPointerUp.bind(this); - this.removeEvent = this.removeEvent.bind(this); + this.onTouchMove = this.onTouchMove.bind(this); + this.onTouchStart = this.onTouchStart.bind(this); + this.onTouchEnd = this.onTouchEnd.bind(this); + this.removeTouch = this.removeTouch.bind(this); + this.addTouch = this.addTouch.bind(this); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerdown", this.onPointerDown); - document.addEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointercancel", this.onPointerUp); - document.addEventListener("touch-used-by-cursor", this.onPointerUp); + document.addEventListener("touchmove", this.onTouchMove); + document.addEventListener("touchstart", this.onTouchStart); + document.addEventListener("touchend", this.onTouchEnd); + document.addEventListener("touchcancel", this.onTouchEnd); + document.addEventListener("touch-used-by-cursor", ev => { + const touch = ev.detail; + this.removeTouch(touch); + this.usedTouch = touch; + }); } - onPointerUp = ev => { - this.removeEvent(ev); - if (this.evCache.length < 2) { - window.LookControlsToggle.toggle(true, this); - this.prevDiff = -1; + onTouchEnd = ev => { + for (let i = 0; i < ev.changedTouches.length; i++) { + const touch = ev.changedTouches[i]; + if (touch.identifier === this.usedTouch.identifier) { + this.usedTouch = { identifier: -1 }; + } + this.removeTouch(touch); } }; - onPointerDown = ev => { - if (ev.isUsedByCursor || ev.clientY / window.innerHeight >= 0.8) { - return; + onTouchStart = ev => { + for (let i = 0; i < ev.touches.length; i++) { + const touch = ev.touches[i]; + if (touch.identifier === this.usedTouch.identifier || touch.clientY / window.innerHeight >= 0.8) { + continue; + } + this.addTouch(touch); } - this.evCache.push(ev); }; - onPointerMove = ev => { - const cache = this.evCache; - - for (var i = 0; i < cache.length; i++) { - if (ev.pointerId === cache[i].pointerId) { - cache[i] = ev; - break; + onTouchMove = ev => { + const cache = this.touchCache; + for (let i = 0; i < ev.touches.length; i++) { + const touch = ev.touches[i]; + if (touch.identifier !== this.usedTouch.identifier) { + this.updateTouch(touch); } } if (cache.length !== 2) { + this.prevDiff = -1; return; } - window.LookControlsToggle.toggle(false, this); + if (window.LookControlsToggle) { + window.LookControlsToggle.toggle(false, this); + } const diff = Pinch.distance(cache[0].clientX, cache[0].clientY, cache[1].clientX, cache[1].clientY); @@ -59,13 +72,38 @@ export default class Pinch { this.prevDiff = diff; }; - removeEvent = ev => { - for (let i = 0; i < this.evCache.length; i++) { - if (this.evCache[i].pointerId == ev.pointerId) { - this.evCache.splice(i, 1); + removeTouch = touch => { + for (let i = 0; i < this.touchCache.length; i++) { + if (this.touchCache[i].identifier === touch.identifier) { + this.touchCache.splice(i, 1); break; } } + if (this.touchCache.length < 2) { + if (window.LookControlsToggle) { + window.LookControlsToggle.toggle(true, this); + } + this.prevDiff = -1; + } + }; + + addTouch = touch => { + for (let i = 0; i < this.touchCache.length; i++) { + if (this.touchCache[i].identifier === touch.identifier) { + return; + } + } + + this.touchCache.push(touch); + }; + + updateTouch = touch => { + for (let i = 0; i < this.touchCache.length; i++) { + if (this.touchCache[i].identifier === touch.identifier) { + this.touchCache[i] = touch; + return; + } + } }; static distance = (x1, y1, x2, y2) => { diff --git a/src/utils/pointer-look-controls.js b/src/utils/pointer-look-controls.js index 49485c8a1..78b23bbc6 100644 --- a/src/utils/pointer-look-controls.js +++ b/src/utils/pointer-look-controls.js @@ -4,17 +4,28 @@ export default class PointerLookControls { this.xSpeed = 0.005; this.ySpeed = 0.003; this.lookControlsEl = lookControlsEl; - this.onPointerDown = this.onPointerDown.bind(this); - this.onPointerMove = this.onPointerMove.bind(this); - this.onPointerUp = this.onPointerUp.bind(this); + this.onTouchStart = this.onTouchStart.bind(this); + this.onTouchMove = this.onTouchMove.bind(this); + this.onTouchEnd = this.onTouchEnd.bind(this); this.getLookControls = this.getLookControls.bind(this); - this.removeEvent = this.removeEvent.bind(this); - document.addEventListener("touch-used-by-cursor", this.onPointerUp); + this.removeTouch = this.removeTouch.bind(this); + this.usedTouch = { identifier: -1 }; + document.addEventListener("touch-used-by-cursor", ev => { + const touch = ev.detail; + this.removeTouch(touch); + this.usedTouch = touch; + }); + this.start = this.start.bind(this); this.stop = this.stop.bind(this); this.getLookControls(); this.cache = []; + + document.addEventListener("touchstart", this.onTouchStart); + document.addEventListener("touchmove", this.onTouchMove); + document.addEventListener("touchend", this.onTouchEnd); + document.addEventListener("touchcancel", this.onTouchEnd); } getLookControls() { @@ -24,64 +35,74 @@ export default class PointerLookControls { } start() { - document.addEventListener("pointerdown", this.onPointerDown); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointercancel", this.onPointerUp); if (!this.lookControls) { this.getLookControls(); } + this.enabled = true; } stop() { - document.removeEventListener("pointerdown", this.onPointerDown); - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.removeEventListener("pointercancel", this.onPointerUp); - this.cache = []; + this.enabled = false; } - onPointerDown(ev) { - if (ev.isUsedByCursor || ev.clientY / window.innerHeight >= 0.8) { - return; + onTouchStart(ev) { + for (let i = 0; i < ev.touches.length; i++) { + let touch = ev.touches[i]; + if (touch.identifier === this.usedTouch.identifier || touch.clientY / window.innerHeight >= 0.8) { + continue; + } } - this.cache.push(ev); } - onPointerMove(ev) { + onTouchMove(ev) { const cache = this.cache; - if (ev.isUsedByCursor || ev.clientY / window.innerHeight >= 0.8) { - return; - } + this.foo = !!this.foo ? this.foo + 1 : 1; + for (let i = 0; i < ev.touches.length; i++) { + let touch = ev.touches[i]; - let cachedEv = null; - for (var i = 0; i < cache.length; i++) { - if (ev.pointerId === cache[i].pointerId) { - cachedEv = cache[i]; - cache[i] = ev; - break; + if (touch.identifier === this.usedTouch.identifier || touch.clientY / window.innerHeight >= 0.8) { + continue; } - } - if (!cachedEv) { - return; - } - const dX = ev.clientX - cachedEv.clientX; - const dY = ev.clientY - cachedEv.clientY; + let cachedTouch = null; + for (var j = 0; j < cache.length; j++) { + if (touch.identifier === cache[j].identifier) { + cachedTouch = cache[j]; + cache[j] = touch; + break; + } + } + if (!cachedTouch) { + this.cache.push(touch); + continue; + } + + if (!this.enabled) { + continue; + } + const dX = touch.clientX - cachedTouch.clientX; + const dY = touch.clientY - cachedTouch.clientY; - this.yawObject.rotation.y -= dX * this.xSpeed; - this.pitchObject.rotation.x -= dY * this.ySpeed; - this.pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitchObject.rotation.x)); + this.yawObject.rotation.y -= dX * this.xSpeed; + this.pitchObject.rotation.x -= dY * this.ySpeed; + this.pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitchObject.rotation.x)); + } } - onPointerUp(ev) { - this.removeEvent(ev); + onTouchEnd(ev) { + for (let i = 0; i < ev.changedTouches.length; i++) { + const touch = ev.changedTouches[i]; + this.removeTouch(touch); + if (touch.identifier === this.usedTouch.identifier) { + this.usedTouch = { identifier: -1 }; + } + } } - removeEvent(ev) { + removeTouch(touch) { const cache = this.cache; for (let i = 0; i < cache.length; i++) { - if (cache[i].pointerId == ev.pointerId) { + if (cache[i].identifier == touch.identifier) { cache.splice(i, 1); break; } diff --git a/yarn.lock b/yarn.lock index e02de0e37..2d452cee6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -174,9 +174,9 @@ aframe-physics-extras@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/aframe-physics-extras/-/aframe-physics-extras-0.1.3.tgz#803e2164fb96c0a80f2d1a81458f3277f262b130" -aframe-physics-system@^3.1.1: +"aframe-physics-system@github:infinitelee/aframe-physics-system#hubs/master": version "3.1.1" - resolved "https://registry.yarnpkg.com/aframe-physics-system/-/aframe-physics-system-3.1.1.tgz#3e6c48f8ce63a1d356a7e302fed51c7b5ad23d22" + resolved "https://codeload.github.com/infinitelee/aframe-physics-system/tar.gz/80a722ddc9496e4fc867fb3662f61b389d0fd4ca" dependencies: browserify "^14.3.0" budo "^10.0.3" -- GitLab