From 21f87d0c31ef348b9520ba9c1302938101025d71 Mon Sep 17 00:00:00 2001
From: Greg Fodor <gfodor@gmail.com>
Date: Sat, 10 Nov 2018 01:28:59 +0000
Subject: [PATCH] Add camera mirroring

---
 src/components/camera-tool.js                 | 49 +++---------
 src/components/mirror-camera-button.js        | 19 +++++
 src/hub.html                                  | 10 ++-
 src/hub.js                                    |  2 +
 src/systems/camera-mirror.js                  | 79 +++++++++++++++++++
 .../userinput/bindings/keyboard-mouse-user.js |  5 ++
 src/systems/userinput/paths.js                |  2 +
 7 files changed, 122 insertions(+), 44 deletions(-)
 create mode 100644 src/components/mirror-camera-button.js
 create mode 100644 src/systems/camera-mirror.js

diff --git a/src/components/camera-tool.js b/src/components/camera-tool.js
index 1849b3ab6..76289192e 100644
--- a/src/components/camera-tool.js
+++ b/src/components/camera-tool.js
@@ -101,11 +101,7 @@ AFRAME.registerComponent("camera-tool", {
   },
 
   remove() {
-    if (this.mirrorCamera) {
-      document.body.classList.remove("mirrored-camera");
-    }
-
-    this.el.sceneEl.renderer.render = this.directRenderFunc;
+    this.el.sceneEl.systems["camera-mirror"].unmirrorCameraAtEl(this.el);
   },
 
   stateAdded(evt) {
@@ -114,39 +110,8 @@ AFRAME.registerComponent("camera-tool", {
     }
   },
 
-  enableCameraMirror() {
-    if (this.mirrorCamera) return;
-    if (!this.el.sceneEl.renderer.vr.enabled) return;
-
-    this.mirrorCamera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 30000);
-    this.el.setObject3D("mirror-camera", this.mirrorCamera);
-    this.mirrorCamera.rotation.set(0, Math.PI, 0);
-
-    this.renderer = this.el.sceneEl.renderer;
-
-    // This overrides the render routine to use the mirrored camera
-    this.directRenderFunc = this.el.sceneEl.renderer.render;
-    const tempScale = new THREE.Vector3();
-    document.body.classList.add("mirrored-camera");
-
-    this.el.sceneEl.renderer.render = (scene, camera, renderTarget) => {
-      const sceneEl = this.el.sceneEl;
-
-      this.directRenderFunc.call(sceneEl.renderer, scene, camera, renderTarget);
-      if (this.playerHead) {
-        tempScale.copy(this.playerHead.scale);
-        this.playerHead.scale.set(1, 1, 1);
-      }
-      sceneEl.renderer.vr.enabled = false;
-      const tmpOnAfterRender = sceneEl.object3D.onAfterRender;
-      delete sceneEl.object3D.onAfterRender;
-      this.directRenderFunc.call(sceneEl.renderer, scene, this.mirrorCamera);
-      sceneEl.object3D.onAfterRender = tmpOnAfterRender;
-      sceneEl.renderer.vr.enabled = true;
-      if (this.playerHead) {
-        this.playerHead.scale.copy(tempScale);
-      }
-    };
+  mirror() {
+    this.el.sceneEl.systems["camera-mirror"].mirrorCameraAtEl(this.el);
   },
 
   tick() {
@@ -161,8 +126,10 @@ AFRAME.registerComponent("camera-tool", {
 
   tock: (function() {
     const tempScale = new THREE.Vector3();
+
     return function tock() {
       const sceneEl = this.el.sceneEl;
+      const cameraMirrorSystem = this.cameraMirrorSystem || sceneEl.systems["camera-mirror"];
       const renderer = this.renderer || sceneEl.renderer;
       const now = performance.now();
 
@@ -182,8 +149,10 @@ AFRAME.registerComponent("camera-tool", {
         renderer.vr.enabled = false;
 
         // Use the direct, non mirrored render function if available
-        if (this.directRenderFunc) {
-          this.directRenderFunc.call(renderer, sceneEl.object3D, this.camera, this.renderTarget, true);
+        if (cameraMirrorSystem) {
+          cameraMirrorSystem
+            .getDirectRenderFunction()
+            .call(renderer, sceneEl.object3D, this.camera, this.renderTarget, true);
         } else {
           renderer.render(sceneEl.object3D, this.camera, this.renderTarget, true);
         }
diff --git a/src/components/mirror-camera-button.js b/src/components/mirror-camera-button.js
new file mode 100644
index 000000000..fc6f867d1
--- /dev/null
+++ b/src/components/mirror-camera-button.js
@@ -0,0 +1,19 @@
+AFRAME.registerComponent("mirror-camera-button", {
+  init() {
+    this.onClick = () => {
+      this.targetEl.components["camera-tool"].mirror();
+    };
+
+    NAF.utils.getNetworkedEntity(this.el).then(networkedEl => {
+      this.targetEl = networkedEl;
+    });
+  },
+
+  play() {
+    this.el.addEventListener("grab-start", this.onClick);
+  },
+
+  pause() {
+    this.el.removeEventListener("grab-start", this.onClick);
+  }
+});
diff --git a/src/hub.html b/src/hub.html
index 47dc4f31e..5260d6da0 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -203,13 +203,15 @@
                     shape="shape: box; halfExtents: 0.22 0.145 0.1; offset: 0 0.02 0"
                     sticky-object="autoLockOnRelease: true; autoLockOnLoad: true; autoLockSpeedLimit: 0;"
                     super-networked-interactable="counter: #camera-counter;"
-                    position-at-box-shape-border="target:.delete-button"
+                    position-at-box-shape-border="target:.camera-menu"
                     set-yxz-order
                     auto-scale-cannon-physics-body
                 >
-                    <a-entity class="delete-button" visible-while-frozen="withinDistance: 3;">
-                        <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity>
-                        <a-entity text=" value:remove; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity>
+                    <a-entity class="camera-menu" visible-while-frozen="withinDistance: 3;">
+                        <a-entity mixin="rounded-text-action-button" mirror-camera-button position="0 0.125 0.01"> </a-entity>
+                        <a-entity text=" value:mirror; width:2.5; align:center;" text-raycast-hack position="0 0.125 0.02"></a-entity>
+                        <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 -0.125 0.01"> </a-entity>
+                        <a-entity text=" value:remove; width:2.5; align:center;" text-raycast-hack position="0 -0.125 0.02"></a-entity>
                     </a-entity>
                 </a-entity>
             </template>
diff --git a/src/hub.js b/src/hub.js
index bffa1d586..23362b8bb 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -59,6 +59,7 @@ import "./components/position-at-box-shape-border";
 import "./components/pinnable";
 import "./components/pin-networked-object-button";
 import "./components/remove-networked-object-button";
+import "./components/mirror-camera-button";
 import "./components/destroy-at-extreme-distances";
 import "./components/gamma-factor";
 import "./components/visible-to-owner";
@@ -82,6 +83,7 @@ import "./systems/personal-space-bubble";
 import "./systems/app-mode";
 import "./systems/exit-on-blur";
 import "./systems/userinput/userinput";
+import "./systems/camera-mirror";
 
 import "./gltf-component-mappings";
 
diff --git a/src/systems/camera-mirror.js b/src/systems/camera-mirror.js
new file mode 100644
index 000000000..780495746
--- /dev/null
+++ b/src/systems/camera-mirror.js
@@ -0,0 +1,79 @@
+import { paths } from "./userinput/paths";
+
+AFRAME.registerSystem("camera-mirror", {
+  tick() {
+    const userinput = this.el.systems.userinput;
+
+    if (userinput.get(paths.actions.camera.exitMirror) && this.mirrorEl) {
+      this.unmirrorCameraAtEl(this.mirrorEl);
+    }
+  },
+
+  // Adds a camera under el, and starts mirroring
+  mirrorCameraAtEl(el) {
+    // TODO probably should explicitly check for immersive mode here.
+    if (AFRAME.utils.device.isMobile()) return;
+    if (this.mirrorEl && this.mirrorEl !== el) this.unmirrorCameraAtEl(this.mirrorEl);
+
+    this.mirrorEl = el;
+    this.mirrorCamera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 30000);
+    this.mirrorCamera.rotation.set(0, Math.PI, 0);
+    el.setObject3D("mirror-camera", this.mirrorCamera);
+
+    const tempScale = new THREE.Vector3();
+
+    const renderer = this.el.renderer;
+
+    if (!this.directRenderFunc) {
+      this.directRenderFunc = renderer.render;
+    }
+
+    const headEl = document.getElementById("player-head");
+    const playerHead = headEl && headEl.object3D;
+    document.body.classList.add("mirrored-camera");
+
+    this.el.sceneEl.renderer.render = (scene, camera, renderTarget) => {
+      const wasVREnabled = this.el.renderer.vr.enabled;
+
+      if (wasVREnabled) {
+        this.directRenderFunc.call(this.el.renderer, scene, camera, renderTarget);
+      }
+
+      if (playerHead) {
+        tempScale.copy(playerHead.scale);
+        playerHead.scale.set(1, 1, 1);
+      }
+      this.el.renderer.vr.enabled = false;
+      const tmpOnAfterRender = this.el.object3D.onAfterRender;
+      delete this.el.object3D.onAfterRender;
+      this.directRenderFunc.call(this.el.renderer, scene, this.mirrorCamera);
+      this.el.object3D.onAfterRender = tmpOnAfterRender;
+      this.el.renderer.vr.enabled = wasVREnabled;
+      if (playerHead) {
+        playerHead.scale.copy(tempScale);
+      }
+    };
+  },
+
+  unmirrorCameraAtEl(el) {
+    if (this.mirrorEl !== el) return;
+
+    if (this.directRenderFunc) {
+      this.el.renderer.render = this.directRenderFunc;
+    }
+
+    el.removeObject3D("mirror-camera");
+    document.body.classList.remove("mirrored-camera");
+
+    this.mirrorEl = null;
+    this.mirrorCamera = null;
+  },
+
+  getDirectRenderFunction() {
+    if (this.directRenderFunc) {
+      return this.directRenderFunc;
+    }
+
+    return this.el.renderer.render;
+  }
+});
diff --git a/src/systems/userinput/bindings/keyboard-mouse-user.js b/src/systems/userinput/bindings/keyboard-mouse-user.js
index 46e5edaf7..a151a21fb 100644
--- a/src/systems/userinput/bindings/keyboard-mouse-user.js
+++ b/src/systems/userinput/bindings/keyboard-mouse-user.js
@@ -77,6 +77,11 @@ export const keyboardMouseUserBindings = {
       dest: { value: paths.actions.boost },
       xform: xforms.copy
     },
+    {
+      src: { value: paths.device.keyboard.key("Escape") },
+      dest: { value: paths.actions.camera.exitMirror },
+      xform: xforms.falling
+    },
     {
       src: { value: paths.device.keyboard.key("q") },
       dest: { value: paths.actions.snapRotateLeft },
diff --git a/src/systems/userinput/paths.js b/src/systems/userinput/paths.js
index 7f38c58cb..451a85ecf 100644
--- a/src/systems/userinput/paths.js
+++ b/src/systems/userinput/paths.js
@@ -60,6 +60,8 @@ paths.actions.leftHand.takeSnapshot = "/actions/leftHandTakeSnapshot";
 paths.actions.leftHand.thumb = "/actions/leftHand/thumbDown";
 paths.actions.leftHand.index = "/actions/leftHand/indexDown";
 paths.actions.leftHand.middleRingPinky = "/actions/leftHand/middleRingPinkyDown";
+paths.actions.camera = {};
+paths.actions.camera.exitMirror = "/actions/cameraExitMirror";
 
 paths.device = {};
 paths.device.mouse = {};
-- 
GitLab