diff --git a/README.md b/README.md index 10e06d6fa6ff8234fab9a0dd532e83a1ea492994..2866a6a1c305647c126aab4b1df48a3e51154b68 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ -# A-Frame Social VR Demo +# Mozilla Social Mixed Reality Client -¯\\_(ツ)_/¯ \ No newline at end of file +A prototype client demonstrating a multi-user experience in WebVR. Built with [A-Frame](https://github.com/aframevr/aframe/) + +## Getting Started + +``` +git clone https://github.com/mozilla/mr-social-client.git +yarn install +npm start +``` \ No newline at end of file diff --git a/package.json b/package.json index 8d71ed2bfe7541df34d17b52deabd74308020c52..293a1ad4c183e2ded74b68ec7dc3e27a1779458e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "social-vr-demo", + "name": "mr-social-client", "version": "0.0.1", "main": "src/index.js", "license": "MPL-2.0", @@ -8,13 +8,16 @@ "build": "webpack --config webpack.prod.js", "prettier": "prettier --write src/**/*.js" }, - "pre-commit": ["prettier"], + "pre-commit": [ + "prettier" + ], "dependencies": { "aframe-input-mapping-component": "https://github.com/fernandojsg/aframe-input-mapping-component", "aframe-teleport-controls": "https://github.com/netpro2k/aframe-teleport-controls#feature/teleport-origin", - "naf-janus-adapter": "^0.1.1", + "naf-janus-adapter": "^0.1.4", "networked-aframe": "https://github.com/netpro2k/networked-aframe#feature/register-adapter", - "pleasejs": "^0.4.2" + "pleasejs": "^0.4.2", + "query-string": "^5.0.1" }, "devDependencies": { "babel-core": "^6.26.0", diff --git a/public/index.html b/public/index.html index d2d284960d26a8287c0e4e13a2315cb92ac32f09..5c5d46b4d292baf9145956bcc633c8c80f827155 100644 --- a/public/index.html +++ b/public/index.html @@ -1,19 +1,20 @@ <html> <head> - <title>A-Frame Social VR Demo</title> + <title>Mozilla Mixed Reality Social Client</title> <script src="https://aframe.io/releases/0.7.0/aframe.js"></script> <script src="./app.bundle.js"></script> </head> <body> - <a-scene networked-scene=" - adapter: janus; - room: 2; - serverURL: wss://quander.me:8989; - audio: true; - debug: true; - connectOnLoad: false" mute-mic="eventSrc: a-scene; toggleEvents: action_mute"> + <a-scene + networked-scene="adapter: janus; + room: 2; + serverURL: wss://quander.me:8989; + audio: true; + debug: true; + connectOnLoad: false;" + mute-mic="eventSrc: a-scene; toggleEvents: action_mute"> <a-assets> <img id="grid" src="assets/grid.png" crossorigin="anonymous" /> @@ -35,11 +36,12 @@ matcolor-audio-feedback="objectName: DodecAvatar_Head_0" scale-audio-feedback avatar-customization + personal-space-invader ></a-entity> </script> <script id="hand-template" type="text/html"> - <a-box class="hand" scale="0.2 0.1 0.3"></a-box> + <a-box class="hand" personal-space-invader scale="0.2 0.1 0.3"></a-box> </script> <script id="nametag-template" type="text/html"> @@ -54,7 +56,7 @@ <a-entity id="player-rig" networked wasd-controls snap-rotation="pivotSrc: #head"> <a-sphere scale="0.1 0.1 0.1"></a-sphere> - <a-entity id="head" camera="userHeight: 1.6" look-controls networked="template:#head-template;showLocalTemplate:false;"></a-entity> + <a-entity id="head" camera="userHeight: 1.6" personal-space-bubble look-controls networked="template:#head-template;showLocalTemplate:false;"></a-entity> <a-entity id="nametag" networked="template:#nametag-template;showLocalTemplate:false;"></a-entity> @@ -80,4 +82,4 @@ </script> </body> -</html> \ No newline at end of file +</html> diff --git a/src/components/audio-feedback.js b/src/components/audio-feedback.js index ee4b802be1ae2e687c2ee9e3b7869a792bac5faa..2b24abd946d7d8fe2ec35c018c4262f64fd00229 100644 --- a/src/components/audio-feedback.js +++ b/src/components/audio-feedback.js @@ -22,12 +22,15 @@ AFRAME.registerComponent("networked-audio-analyser", { return NAF.connection.adapter.getMediaStream(ownerId); }) .then(stream => { + if (!stream) { + return; + } + const ctx = THREE.AudioContext.getContext(); const source = ctx.createMediaStreamSource(stream); this.analyser = ctx.createAnalyser(); this.levels = new Uint8Array(this.analyser.frequencyBinCount); source.connect(this.analyser); - console.log(source, this.analyser); }); }, diff --git a/src/index.js b/src/index.js index 690a9e69e8fc1fda51aa2abdf31b75a405d546d9..df8b464678bfef1eafe5530b38361f279d541557 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ +import queryString from "query-string"; + import "networked-aframe"; import "naf-janus-adapter"; import "aframe-teleport-controls"; @@ -12,6 +14,8 @@ import "./components/avatar-customization"; import "./components/mute-state-indicator"; import "./components/hand-controls-visibility"; +import "./systems/personal-space-bubble"; + import { generateName } from "./utils"; NAF.schemas.add({ @@ -55,15 +59,26 @@ AFRAME.registerInputMappings({ } } }); -window.onSceneLoad = function() { + +const promptForName = function() { var username = generateName(); do { username = prompt("Choose a username", username); } while (!(username && username.length)); + return username; +}; + +const qs = queryString.parse(location.search); +window.onSceneLoad = function() { + const scene = document.querySelector("a-scene"); + + if (qs.room && !isNaN(parseInt(qs.room))) { + scene.setAttribute("networked-scene", "room", parseInt(qs.room)); + } - var player = document.getElementById("player-rig"); - var myNametag = player.querySelector(".nametag"); + const username = promptForName(); // promptForName is blocking + const myNametag = document.querySelector("#player-rig .nametag"); myNametag.setAttribute("text", "value", username); - document.querySelector("a-scene").components["networked-scene"].connect(); + scene.components["networked-scene"].connect(); }; diff --git a/src/systems/personal-space-bubble.js b/src/systems/personal-space-bubble.js new file mode 100644 index 0000000000000000000000000000000000000000..9087ec5a4614c0d80a400e13cca0382bf69236a0 --- /dev/null +++ b/src/systems/personal-space-bubble.js @@ -0,0 +1,93 @@ +var invaderPos = new AFRAME.THREE.Vector3(); +var bubblePos = new AFRAME.THREE.Vector3(); + +AFRAME.registerSystem("personal-space-bubble", { + init() { + this.invaders = []; + this.bubbles = []; + }, + + registerBubble(el) { + this.bubbles.push(el); + }, + + unregisterBubble(el) { + var index = this.bubbles.indexOf(el); + + if (index !== -1) { + this.bubbles.splice(index, 1); + } + }, + + registerInvader(el) { + var networkedEl = NAF.utils.getNetworkedEntity(el); + var owner = NAF.utils.getNetworkOwner(networkedEl); + + if (owner !== NAF.clientId) { + this.invaders.push(el); + } + }, + + unregisterInvader(el) { + var index = this.invaders.indexOf(el); + + if (index !== -1) { + this.invaders.splice(index, 1); + } + }, + + tick() { + // Update matrix positions once for each space bubble and space invader + for (var i = 0; i < this.bubbles.length; i++) { + this.bubbles[i].object3D.updateMatrixWorld(true); + } + + for (var i = 0; i < this.invaders.length; i++) { + this.invaders[i].object3D.updateMatrixWorld(true); + } + + // Loop through all of the space bubbles (usually one) + for (var i = 0; i < this.bubbles.length; i++) { + var bubble = this.bubbles[i]; + + bubblePos.setFromMatrixPosition(bubble.object3D.matrixWorld); + + var radius = bubble.components["personal-space-bubble"].data.radius; + var radiusSquared = radius * radius; + + // Hide the invader if inside the bubble + for (var j = 0; j < this.invaders.length; j++) { + var invader = this.invaders[j]; + + invaderPos.setFromMatrixPosition(invader.object3D.matrixWorld); + + var distanceSquared = bubblePos.distanceTo(invaderPos); + + invader.object3D.visible = distanceSquared > radiusSquared; + } + } + } +}); + +AFRAME.registerComponent("personal-space-invader", { + init() { + this.el.sceneEl.systems["personal-space-bubble"].registerInvader(this.el); + }, + + remove() { + this.el.sceneEl.systems["personal-space-bubble"].unregisterInvader(this.el); + } +}); + +AFRAME.registerComponent("personal-space-bubble", { + schema: { + radius: { type: "number", default: 0.8 } + }, + init() { + this.system.registerBubble(this.el); + }, + + remove() { + this.system.unregisterBubble(this.el); + } +}); diff --git a/yarn.lock b/yarn.lock index 3608d9e26071cf6a8d1c9b8a142dd022d8004386..bf1721606038eed8522bafbdd999efa9871af59c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1376,6 +1376,10 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -2728,9 +2732,9 @@ multicast-dns@^6.0.1: dns-packet "^1.0.1" thunky "^0.1.0" -naf-janus-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/naf-janus-adapter/-/naf-janus-adapter-0.1.1.tgz#9798e11196e38edb04862aebc8213e9345895f8c" +naf-janus-adapter@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/naf-janus-adapter/-/naf-janus-adapter-0.1.4.tgz#3e6e2022e8f6d5b7f6221fcec8b36a1e1e13be44" dependencies: minijanus "^0.1.4" @@ -3174,6 +3178,14 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +query-string@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.0.1.tgz#6e2b86fe0e08aef682ecbe86e85834765402bd88" + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -3672,6 +3684,10 @@ stream-http@^2.3.1: to-arraybuffer "^1.0.0" xtend "^4.0.0" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"