AFRAME.registerComponent("in-world-hud", { schema: { haptic: { type: "selector" }, raycaster: { type: "selector" } }, init() { this.bg = this.el.querySelector(".bg"); this.mic = this.el.querySelector(".mic"); this.nametag = this.el.querySelector(".nametag"); this.nametag.object3DMap.text.material.depthTest = false; this.avatar = this.el.querySelector(".avatar"); this.data.raycaster.components.line.material.depthTest = false; const muted = this.el.sceneEl.is("muted"); this.mic.setAttribute("src", muted ? "#muted" : "#unmuted"); const avatarScale = "0.1 0.1 0.1"; const flipXAvatarScale = "-" + avatarScale; const scene = this.el.sceneEl; this.onUsernameChanged = this.onUsernameChanged.bind(this); scene.addEventListener("username-changed", this.onUsernameChanged); this.onNametagHovered = () => { this.nametag.setAttribute("color", "#00eeee"); this.data.haptic.emit("haptic_pulse", { intensity: "low" }); }; this.onNametagUnhovered = () => { this.nametag.setAttribute("color", "white"); }; this.onAvatarHovered = () => { this.avatar.setAttribute("scale", flipXAvatarScale); this.data.haptic.emit("haptic_pulse", { intensity: "low" }); }; this.onAvatarUnhovered = () => { this.avatar.setAttribute("scale", avatarScale); }; this.onMicHover = () => { this.hoveredOnMic = true; const muted = scene.is("muted"); this.mic.setAttribute("src", muted ? "#unmuted" : "#muted"); this.data.haptic.emit("haptic_pulse", { intensity: "low" }); }; this.showCorrectMuteState = () => { const muted = this.el.sceneEl.is("muted"); this.mic.setAttribute("src", muted ? "#muted" : "#unmuted"); }; this.onStateChange = evt => { if (evt.detail !== "muted") return; this.showCorrectMuteState(); }; this.onMicHoverExit = () => { this.hoveredOnMic = false; this.showCorrectMuteState(); }; this.onSelect = evt => { if (this.hoveredOnMic) { this.el.emit("action_mute"); this.data.haptic.emit("haptic_pulse", { intensity: "low" }); } }; this.onClick = () => { this.el.emit("action_select_hud_item"); }; this.onAudioFrequencyChange = e => { const red = 1.0 - e.detail.volume / 10.0; this.mic.object3DMap.mesh.material.color = { r: red, g: 9, b: 9 }; }; this.el.sceneEl.addEventListener("mediaStream", evt => { this.ms = evt.detail.ms; const ctx = THREE.AudioContext.getContext(); const source = ctx.createMediaStreamSource(this.ms); this.analyser = ctx.createAnalyser(); this.levels = new Uint8Array(this.analyser.frequencyBinCount); source.connect(this.analyser); }); }, play() { this.mic.addEventListener("raycaster-intersected", this.onMicHover); this.mic.addEventListener("raycaster-intersected-cleared", this.onMicHoverExit); this.nametag.addEventListener("raycaster-intersected", this.onNametagHovered); this.nametag.addEventListener("raycaster-intersected-cleared", this.onNametagUnhovered); this.avatar.addEventListener("raycaster-intersected", this.onAvatarHovered); this.avatar.addEventListener("raycaster-intersected-cleared", this.onAvatarUnhovered); this.el.sceneEl.addEventListener("stateadded", this.onStateChange); this.el.sceneEl.addEventListener("stateremoved", this.onStateChange); this.el.sceneEl.addEventListener("action_select_hud_item", this.onSelect); this.el.addEventListener("click", this.onClick); this.el.sceneEl.addEventListener("micAudio", this.onAudioFrequencyChange); }, pause() { this.nametag.removeEventListener("raycaster-intersected", this.onNametagHovered); this.nametag.removeEventListener("raycaster-intersected-cleared", this.onNametagUnhovered); this.mic.removeEventListener("raycaster-intersected", this.onMicHover); this.mic.removeEventListener("raycaster-intersected-cleared", this.onMicHoverExit); this.avatar.removeEventListener("raycaster-intersected", this.onAvatarHovered); this.avatar.removeEventListener("raycaster-intersected-cleared", this.onAvatarUnhovered); this.el.sceneEl.removeEventListener("stateadded", this.onStateChange); this.el.sceneEl.removeEventListener("stateremoved", this.onStateChange); this.el.sceneEl.removeEventListener("action_select_hud_item", this.onSelect); this.el.removeEventListener("click", this.onClick); this.el.sceneEl.removeEventListener("micAudio", this.onAudioFrequencyChange); }, tick: function(t, dt) { if (!this.analyser) return; this.analyser.getByteFrequencyData(this.levels); let sum = 0; for (let i = 0; i < this.levels.length; i++) { sum += this.levels[i]; } this.volume = sum / this.levels.length; this.el.emit("micAudio", { volume: this.volume, levels: this.levels }); }, onUsernameChanged(evt) { let width; if (evt.detail.username.length == 0) { width = 1; } else { width = 40 / evt.detail.username.length; } const maxWidth = 6; if (width > maxWidth) { width = maxWidth; } this.nametag.setAttribute("text", "width", width); this.nametag.setAttribute("text", "value", evt.detail.username); this.nametag.components["text"].updateFont(); } });